Próbuję użyć ThreadPoolExecutor
klasy Javy do uruchamiania dużej liczby ciężkich zadań z ustaloną liczbą wątków. Każde z zadań ma wiele miejsc, w których może się nie powieść z powodu wyjątków.
Podklasowałem ThreadPoolExecutor
i przesłoniłem afterExecute
metodę, która ma zapewnić nieprzechwycone wyjątki napotkane podczas uruchamiania zadania. Nie mogę jednak sprawić, by działało.
Na przykład:
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();
}
}
Dane wyjściowe tego programu to „Wszystko w porządku - sytuacja normalna!” nawet jeśli tylko Runnable przesłany do puli wątków zgłasza wyjątek. Masz jakieś wskazówki co się tutaj dzieje?
Dzięki!
Odpowiedzi:
Z dokumentów :
Gdy prześlesz Runnable, zostanie on zapakowany w Przyszłość.
Twój afterExecute powinien wyglądać mniej więcej tak:
źródło
future.isDone()
? PonieważafterExecute
jest uruchamiany poRunnable
zakończeniu, zakładam, żefuture.isDone()
zawsze zwracatrue
.OSTRZEŻENIE : Należy zauważyć, że to rozwiązanie zablokuje wątek wywołujący.
Jeśli chcesz przetworzyć wyjątki zgłoszone przez zadanie, zazwyczaj lepiej jest użyć
Callable
niżRunnable
.Callable.call()
wolno zgłaszać sprawdzone wyjątki, które są propagowane z powrotem do wątku wywołującego:Jeśli
Callable.call()
zgłosi wyjątek, zostanie on zawinięty wExecutionException
i rzucony przezFuture.get()
.Jest to prawdopodobnie znacznie lepsze niż podklasowanie
ThreadPoolExecutor
. Daje to również możliwość ponownego przesłania zadania, jeśli wyjątek jest możliwy do odzyskania.źródło
future.get()
zostanie wywołana jego przeciążona wersja.Wyjaśnienie tego zachowania znajduje się w javadoc dla afterExecute :
źródło
Obejrzałem to, owijając dostarczony plik wykonywalny przesłany do executora.
źródło
whenComplete()
metodyCompletableFuture
.Korzystam
VerboseRunnable
z klasy z jcabi-log , która połyka wszystkie wyjątki i rejestruje je. Bardzo wygodne, na przykład:źródło
Innym rozwiązaniem byłoby użycie ManagedTask i ManagedTaskListener .
Potrzebujesz Callable lub Runnable, który implementuje interfejs ManagedTask .
Metoda
getManagedTaskListener
zwraca żądaną instancję.I wdrożyć w ManagedTaskListener ten
taskDone
sposób:Więcej informacji na temat cyklu życia zarządzanego zadania i detektora .
źródło
To działa
Stworzy Executora z jednym wątkiem, który może uzyskać wiele zadań; i poczeka, aż bieżące zakończy wykonywanie, a rozpocznie się od następnego
W przypadku nieuczciwego błędu lub wyjątku uncaughtExceptionHandler go złapie
źródło
Jeśli chcesz monitorować wykonanie zadania, możesz zakręcić 1 lub 2 wątkami (być może więcej w zależności od obciążenia) i użyć ich do pobrania zadań z opakowania ExecutionCompletionService.
źródło
Jeśli twój
ExecutorService
pochodzi z zewnętrznego źródła (tzn. Nie jest możliwe podklasęThreadPoolExecutor
i zastąpienieafterExecute()
), możesz użyć dynamicznego serwera proxy, aby osiągnąć pożądane zachowanie:źródło
Wynika to z tego, że
AbstractExecutorService :: submit
owijasz sięrunnable
wRunnableFuture
(nic pozaFutureTask
) jak poniżejNastępnie
execute
przekaże goWorker
iWorker.run()
zadzwoni poniżej.źródło
Jest to podobne do rozwiązania mmm, ale nieco bardziej zrozumiałe. Niech twoje zadania rozszerzą klasę abstrakcyjną, która otacza metodę run ().
źródło
Zamiast podklasowania ThreadPoolExecutor, dostarczyłbym mu instancję ThreadFactory, która tworzy nowe wątki i udostępnia im UncaughtExceptionHandler
źródło