การจัดการข้อยกเว้นจากงาน Java ExecutorService


213

ฉันกำลังพยายามใช้ThreadPoolExecutorคลาสของ Java เพื่อทำงานหนักจำนวนมากด้วยจำนวนเธรดที่แน่นอน แต่ละงานมีหลายสถานที่ซึ่งอาจล้มเหลวเนื่องจากข้อยกเว้น

ฉันได้ subclassed ThreadPoolExecutorและฉันได้แทนที่afterExecuteวิธีที่ควรจะให้ข้อยกเว้นที่ไม่ถูกตรวจพบในขณะที่ทำงาน อย่างไรก็ตามฉันไม่สามารถใช้งานได้

ตัวอย่างเช่น:

public class ThreadPoolErrors extends ThreadPoolExecutor {
    public ThreadPoolErrors() {
        super(  1, // core threads
                1, // max threads
                1, // timeout
                TimeUnit.MINUTES, // timeout units
                new LinkedBlockingQueue<Runnable>() // work queue
        );
    }

    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if(t != null) {
            System.out.println("Got an error: " + t);
        } else {
            System.out.println("Everything's fine--situation normal!");
        }
    }

    public static void main( String [] args) {
        ThreadPoolErrors threadPool = new ThreadPoolErrors();
        threadPool.submit( 
                new Runnable() {
                    public void run() {
                        throw new RuntimeException("Ouch! Got an error.");
                    }
                }
        );
        threadPool.shutdown();
    }
}

ผลลัพธ์จากโปรแกรมนี้คือ "ทุกอย่างเรียบร้อย - สถานการณ์ปกติ!" แม้ว่า Runnable เดียวที่ส่งไปยังเธรดพูลจะมีข้อยกเว้น เบาะแสอะไรที่เกิดขึ้นที่นี่?

ขอบคุณ!


คุณไม่เคยถามถึงอนาคตของงานสิ่งที่เกิดขึ้นที่นั่น ผู้ให้บริการหรือโปรแกรมทั้งหมดจะไม่ผิดพลาด ข้อยกเว้นถูกตรวจจับและอยู่ภายใต้ ExecutionException และเขาจะกลับมาใหม่ถ้าคุณโทรหาในอนาคตรับ () PS: The Future.isDone () [โปรดอ่านชื่อ API จริง] จะกลับมาจริงแม้ในขณะที่ runnable เสร็จสิ้นผิดพลาด เพราะงานที่ทำจริง
ใจบัณฑิต

คำตอบ:


156

จากเอกสาร :

หมายเหตุ: เมื่อการกระทำถูกล้อมรอบในงาน (เช่น FutureTask) อย่างชัดเจนหรือผ่านวิธีการเช่นส่งวัตถุงานเหล่านี้จะตรวจจับและบำรุงรักษาข้อยกเว้นการคำนวณดังนั้นจึงไม่ทำให้เกิดการยกเลิกอย่างกะทันหันและข้อยกเว้นภายในจะไม่ถูกส่งผ่านไปยังวิธีนี้ .

เมื่อคุณส่ง Runnable มันจะถูกห่อในอนาคต

afterExecute ของคุณควรเป็นดังนี้:

public final class ExtendedExecutor extends ThreadPoolExecutor {

    // ...

    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if (t == null && r instanceof Future<?>) {
            try {
                Future<?> future = (Future<?>) r;
                if (future.isDone()) {
                    future.get();
                }
            } catch (CancellationException ce) {
                t = ce;
            } catch (ExecutionException ee) {
                t = ee.getCause();
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
        }
        if (t != null) {
            System.out.println(t);
        }
    }
}

7
ขอบคุณฉันลงเอยด้วยการใช้โซลูชันนี้ นอกจากนี้ในกรณีที่ใครสนใจ: คนอื่น ๆ ไม่แนะนำให้ subclassing ExecutorService แต่ฉันทำต่อไปเพราะฉันต้องการตรวจสอบงานที่ทำเสร็จแทนที่จะรอให้พวกเขาทั้งหมดยุติแล้วโทรหา get () บน Futures ที่ส่งคืนทั้งหมด .
ทอม

1
อีกวิธีหนึ่งในการ subclassing executor ก็คือ subclass FutureTask และแทนที่ method 'done'
nos

1
ทอม >> คุณช่วยกรุณาโพสต์โค้ดของคุณตัวอย่างที่คุณ subclassed ExecutorService ในการตรวจสอบงานที่พวกเขาเสร็จสิ้น ...
jagamot

1
คำตอบนี้จะไม่ทำงานหากคุณใช้ ComplableFuture.runAsync เนื่องจาก afterExecute จะมีออบเจ็กต์ที่เป็นแพคเกจส่วนตัวและไม่สามารถเข้าถึงการโยนได้ ฉันได้รับมันโดยการวางสาย ดูคำตอบของฉันด้านล่าง
mmm

2
เราต้องตรวจสอบว่าอนาคตใช้งานเสร็จfuture.isDone()หรือไม่? นับตั้งแต่afterExecuteมีการเรียกใช้หลังจากที่Runnableเสร็จสิ้นแล้วผมถือว่าเสมอผลตอบแทนfuture.isDone() true
Searene

248

คำเตือน : ควรสังเกตว่าโซลูชันนี้จะบล็อกเธรดการโทร


หากคุณต้องการยกเว้นกระบวนการโยนโดยงานแล้วมันเป็นเรื่องปกติดีกว่าการใช้มากกว่าCallableRunnable

Callable.call() ได้รับอนุญาตให้โยนข้อยกเว้นที่เลือกและสิ่งเหล่านี้จะแพร่กระจายกลับไปยังเธรดการโทร:

Callable task = ...
Future future = executor.submit(task);
try {
   future.get();
} catch (ExecutionException ex) {
   ex.getCause().printStackTrace();
}

หากCallable.call()ผิดข้อยกเว้นนี้จะถูกห่อในและโยนโดยExecutionExceptionFuture.get()

นี่คือแนวโน้มที่จะมากกว่าที่จะ ThreadPoolExecutorsubclassing นอกจากนี้ยังเปิดโอกาสให้คุณส่งงานอีกครั้งหากข้อยกเว้นเป็นงานที่สามารถกู้คืนได้


5
> Callable.call () ได้รับอนุญาตให้โยนข้อยกเว้นที่เลือกและสิ่งเหล่านี้จะถูกส่งกลับไปยังเธรดการโทร: โปรดทราบว่าข้อยกเว้นที่ส่งออกไปจะเผยแพร่ไปยังเธรดการโทรเฉพาะในกรณีที่future.get()เรียกรุ่นที่โอเวอร์โหลด
nhylated

16
มันสมบูรณ์แบบ แต่จะทำอย่างไรถ้าฉันเรียกใช้งานแบบขนานและไม่ต้องการบล็อกการทำงาน?
กริกอ Kislin

43
อย่าใช้โซลูชันนี้เนื่องจากวัตถุประสงค์ทั้งหมดของการใช้ ExecutorService ExecutorService เป็นกลไกการดำเนินการแบบอะซิงโครนัสซึ่งมีความสามารถในการดำเนินงานในพื้นหลัง ถ้าคุณเรียกใช้ future.get () ทันทีหลังจากดำเนินการมันจะบล็อกเธรดการโทรจนกว่างานจะเสร็จสิ้น
user1801374

2
การแก้ปัญหานี้ไม่ควรจัดอันดับสูง Future.get () ทำงานแบบซิงโครนัสและจะทำหน้าที่เป็นตัวบล็อกจนกว่า Runnable หรือ Callable จะถูกประหารชีวิตและตามที่ระบุไว้ข้างต้นเอาชนะจุดประสงค์ของการใช้บริการ Executor
Super Hans

2
ดังที่ #nhylated ชี้ให้เห็นสิ่งนี้สมควรได้รับ jdk BUG ถ้า Future.get () ไม่ถูกเรียกใช้ข้อยกเว้นใด ๆ ที่ไม่ได้รับการเรียกจาก Callable จะถูกเพิกเฉยอย่างเงียบ ๆ การออกแบบที่แย่มาก .... ใช้เวลา 1+ วันในการหาห้องสมุดที่ใช้สิ่งนี้และ jdk ก็ละเว้นข้อยกเว้นอย่างเงียบ ๆ และสิ่งนี้ยังคงมีอยู่ใน jdk12
เบ็นเจียง

18

คำอธิบายสำหรับพฤติกรรมนี้อยู่ในjavadoc สำหรับ afterExecute :

หมายเหตุ: เมื่อการกระทำถูกล้อมรอบในงาน (เช่น FutureTask) อย่างชัดเจนหรือผ่านวิธีการเช่นส่งวัตถุงานเหล่านี้จะตรวจจับและบำรุงรักษาข้อยกเว้นการคำนวณดังนั้นจึงไม่ทำให้เกิดการยกเลิกอย่างกะทันหันและข้อยกเว้นภายในจะไม่ถูกส่งผ่านไปยังวิธีนี้ .


10

ฉันได้รอบมันโดยห่อที่ runnable ที่ให้มาส่งไปยังผู้บริหาร

CompletableFuture.runAsync(() -> {
        try {
              runnable.run();
        } catch (Throwable e) {
              Log.info(Concurrency.class, "runAsync", e);
        }
}, executorService);

3
คุณสามารถปรับปรุงการอ่านโดยใช้วิธีการwhenComplete() CompletableFuture
Eduard Wirch

@EduardWirch ใช้งานได้ แต่คุณไม่สามารถทิ้งข้อยกเว้นจาก whenComplete ()
Akshat

7

ฉันใช้VerboseRunnableคลาสจากjcabi-logซึ่งกลืนทุกข้อยกเว้นและบันทึกพวกมัน สะดวกมากเช่น:

import com.jcabi.log.VerboseRunnable;
scheduler.scheduleWithFixedDelay(
  new VerboseRunnable(
    Runnable() {
      public void run() { 
        // the code, which may throw
      }
    },
    true // it means that all exceptions will be swallowed and logged
  ),
  1, 1, TimeUnit.MILLISECONDS
);

3

วิธีการแก้ปัญหาอีกก็จะไปใช้ManagedTaskและManagedTaskListener

คุณจำเป็นต้องมีCallableหรือRunnableซึ่งการดำเนินการอินเตอร์เฟซManagedTask

เมธอดgetManagedTaskListenerส่งคืนอินสแตนซ์ที่คุณต้องการ

public ManagedTaskListener getManagedTaskListener() {

และคุณใช้ในManagedTaskListenertaskDoneวิธีการ:

@Override
public void taskDone(Future<?> future, ManagedExecutorService executor, Object task, Throwable exception) {
    if (exception != null) {
        LOGGER.log(Level.SEVERE, exception.getMessage());
    }
}

รายละเอียดเพิ่มเติมเกี่ยวกับวงจรชีวิตของงานการจัดการและผู้ฟัง


2

วิธีนี้ใช้ได้ผล

  • มันมาจาก SingleThreadExecutor แต่คุณสามารถปรับได้อย่างง่ายดาย
  • โค้ด Java 8 lamdas แต่ง่ายต่อการแก้ไข

มันจะสร้าง Executor ด้วยเธรดเดียวที่สามารถทำงานได้มากมาย และจะรอให้การดำเนินการปัจจุบันสิ้นสุดลงเพื่อเริ่มต้นด้วยการถัดไป

ในกรณีที่มีข้อผิดพลาด uncaugth หรือข้อยกเว้นuncaughtExceptionHandlerจะจับมัน

ชั้นสุดท้ายสาธารณะ SingleThreadExecutorWithExceptions {

    สาธารณะคง ExecutorService newSingleThreadExecutorWithExceptions (หัวข้อสุดท้ายสุดท้ายที่จับการยกเว้นแฮ็กเกอร์ uncaughtExceptionHandler) {

        ThreadFactory factory = (เรียกใช้รันได้) -> {
            thread ล่าสุด newThread = new Thread (รันได้, "SingleThreadExecutorWithExceptions");
            newThread.setUncaughtExceptionHandler ((เธรดสุดท้ายสุดท้ายเธรดเธรดสุดท้าย Throwable Throwable) -> {
                uncaughtExceptionHandler.uncaughtException (caugthThread, Throwable);
            });
            กลับมาใหม่ด้าย;
        };
        ส่งคืน FinalizableDelegatedExecutorService ใหม่
                (ThreadPoolExecutor ใหม่ (1, 1,
                        0L, TimeUnit.MILLISECONDS,
                        ใหม่ LinkedBlockingQueue ()
                        โรงงาน){


                    โมฆะที่ได้รับการป้องกัน afterExecute (Runnable runnable, Throwable Throwable) {
                        super.afterExecute (รันได้, โยนได้);
                        if (Throwable == null && สามารถเรียกใช้อินสแตนซ์ของ Future) {
                            ลอง {
                                Future Future = (Future) ที่รันได้;
                                if (future.isDone ()) {
                                    future.get ();
                                }
                            } catch (CancellationException ce) {
                                Throwable = ce;
                            } catch (ExecutionException ee) {
                                Throwable = ee.getCause ();
                            } catch (InterruptException ie) {
                                Thread.currentThread () ขัดจังหวะ (). // ละเว้น / รีเซ็ต
                            }
                        }
                        if (throwable! = null) {
                            uncaughtExceptionHandler.uncaughtException (Thread.currentThread () Throwable);
                        }
                    }
                });
    }



    คลาสแบบคงที่ส่วนตัว FinalizableDelegatedExecutorService
            ขยาย DelegatedExecutorService {
        FinalizableDelegatedExecutorService (ExecutorService ตัวจัดการ) {
            ซุปเปอร์ (ปฏิบัติการ);
        }
        โมฆะที่ได้รับการป้องกันจะเสร็จสิ้น () {
            super.shutdown ();
        }
    }

    / **
     * คลาส wrapper ที่แสดงเฉพาะเมธอด ExecutorService
     * ของการใช้งาน ExecutorService
     * /
    คลาสคงที่ส่วนตัว DelegatedExecutorService ขยาย AbstractExecutorService {
        ExecutorService สุดท้ายส่วนตัว e;
        DelegatedExecutorService (ExecutorService executor) {e = ผู้ปฏิบัติการ; }
        โมฆะสาธารณะดำเนินการ (คำสั่ง Runnable) {e.execute (คำสั่ง); }
        โมฆะสาธารณะปิด () {e.shutdown (); }
        รายการสาธารณะ shutdownNow () {return e.shutdownNow (); }
        บูลีนสาธารณะ isShutdown () {return e.isShutdown (); }
        บูลีนสาธารณะ isTerminated () {return e.isTerminated (); }
        บูลีนสาธารณะกำลังรอการประกาศ (การหมดเวลานานหน่วย TimeUnit)
                พ่น InterruptedException {
            ส่งคืน e.awaitTermination (หมดเวลาหน่วย);
        }
        อนาคตส่ง (งานที่รันได้) {
            return e.submit (ภารกิจ);
        }
        อนาคตส่ง (งานที่เรียกได้) {
            return e.submit (ภารกิจ);
        }
        อนาคตส่ง (งานที่รันได้, ผลลัพธ์ T) {
            return e.submit (ภารกิจผลลัพธ์);
        }
        รายการสาธารณะ> invokeAll (คอลเลกชัน> งาน)
                พ่น InterruptedException {
            ส่งคืน e.invokeAll (งาน);
        }
        รายการสาธารณะ> invokeAll (คอลเลกชัน> งาน,
                                             หมดเวลานานหน่วย TimeUnit)
                พ่น InterruptedException {
            ส่งคืน e.invokeAll (ภารกิจหมดเวลาหน่วย)
        }
        public T invokeAny (คอลเลกชัน> งาน)
                พ่น InterruptedException, ExecutionException {
            ส่งคืน e.invokeAny (งาน);
        }
        public T invokeAny (คอลเลกชัน> งาน,
                               หมดเวลานานหน่วย TimeUnit)
                พ่น InterruptedException, ExecutionException, TimeoutException {
            ส่งคืน e.invokeAny (ภารกิจหมดเวลาหน่วย);
        }
    }



    ส่วนตัว SingleThreadExecutorWithExceptions () {}
}

การใช้ขั้นสุดท้ายนั้นค่อนข้างไม่แน่นอนเนื่องจากมันจะถูกเรียกว่า "ภายหลังเมื่อตัวรวบรวมขยะรวบรวม" (หรืออาจจะไม่ใช่ในกรณีของเธรด, dunno) ...
rogerdpack

1

หากคุณต้องการตรวจสอบการทำงานของงานคุณสามารถหมุน 1 หรือ 2 เธรด (อาจจะมากกว่านั้นขึ้นอยู่กับโหลด) และใช้มันเพื่อทำงานจาก ExecutionCompletionService wrapper


0

หากคุณExecutorServiceมาจากแหล่งภายนอก (เช่นเป็นไปไม่ได้ที่จะทำ subclass ThreadPoolExecutorและ override afterExecute()) คุณสามารถใช้ dynamic proxy เพื่อให้ได้พฤติกรรมตามที่ต้องการ:

public static ExecutorService errorAware(final ExecutorService executor) {
    return (ExecutorService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
            new Class[] {ExecutorService.class},
            (proxy, method, args) -> {
                if (method.getName().equals("submit")) {
                    final Object arg0 = args[0];
                    if (arg0 instanceof Runnable) {
                        args[0] = new Runnable() {
                            @Override
                            public void run() {
                                final Runnable task = (Runnable) arg0;
                                try {
                                    task.run();
                                    if (task instanceof Future<?>) {
                                        final Future<?> future = (Future<?>) task;

                                        if (future.isDone()) {
                                            try {
                                                future.get();
                                            } catch (final CancellationException ce) {
                                                // Your error-handling code here
                                                ce.printStackTrace();
                                            } catch (final ExecutionException ee) {
                                                // Your error-handling code here
                                                ee.getCause().printStackTrace();
                                            } catch (final InterruptedException ie) {
                                                Thread.currentThread().interrupt();
                                            }
                                        }
                                    }
                                } catch (final RuntimeException re) {
                                    // Your error-handling code here
                                    re.printStackTrace();
                                    throw re;
                                } catch (final Error e) {
                                    // Your error-handling code here
                                    e.printStackTrace();
                                    throw e;
                                }
                            }
                        };
                    } else if (arg0 instanceof Callable<?>) {
                        args[0] = new Callable<Object>() {
                            @Override
                            public Object call() throws Exception {
                                final Callable<?> task = (Callable<?>) arg0;
                                try {
                                    return task.call();
                                } catch (final Exception e) {
                                    // Your error-handling code here
                                    e.printStackTrace();
                                    throw e;
                                } catch (final Error e) {
                                    // Your error-handling code here
                                    e.printStackTrace();
                                    throw e;
                                }
                            }
                        };
                    }
                }
                return method.invoke(executor, args);
            });
}

0

นี้เป็นเพราะAbstractExecutorService :: submitมีการห่อของคุณrunnableลงในRunnableFuture( แต่ไม่มีอะไรFutureTask) เช่นนี้

AbstractExecutorService.java

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null); /////////HERE////////
    execute(ftask);
    return ftask;
}

จากนั้นexecuteจะส่งผ่านไปยังWorkerและWorker.run()จะโทรไปด้านล่าง

ThreadPoolExecutor.java

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();           /////////HERE////////
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

สุดท้ายในสายรหัสดังกล่าวจะเรียกtask.run(); FutureTask.run()นี่คือโค้ดตัวจัดการข้อยกเว้นเนื่องจากสิ่งนี้คุณไม่ได้รับข้อยกเว้นที่คาดไว้

class FutureTask<V> implements RunnableFuture<V>

public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {   /////////HERE////////
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

0

สิ่งนี้คล้ายกับโซลูชันของ mmm แต่เข้าใจได้มากกว่า ให้งานของคุณขยายคลาสนามธรรมที่ล้อมรอบเมธอด run ()

public abstract Task implements Runnable {

    public abstract void execute();

    public void run() {
      try {
        execute();
      } catch (Throwable t) {
        // handle it  
      }
    }
}


public MySampleTask extends Task {
    public void execute() {
        // heavy, error-prone code here
    }
}

-4

แทนที่จะเป็นคลาสย่อย ThreadPoolExecutor ฉันจะให้อินสแตนซ์ของThreadFactoryที่สร้างเธรดใหม่และให้พวกเขาด้วยUncaughtExceptionHandler


3
ฉันลองวิธีนี้เช่นกัน แต่วิธี uncaughtException ดูเหมือนจะไม่ถูกเรียก ฉันเชื่อว่าเป็นเพราะเธรดผู้ปฏิบัติงานในคลาส ThreadPoolExecutor กำลังตรวจจับข้อยกเว้น
Tom

5
ไม่ได้เรียกใช้เมธอด uncaughtException เนื่องจากวิธีการส่งของ ExecutorService กำลังตัด Callable / Runnable ในอนาคต ข้อยกเว้นกำลังถูกจับที่นั่น
Emil Sit
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.