Обробка винятків із завдань Java ExecutorService


213

Я намагаюся використовувати ThreadPoolExecutorклас Java для виконання великої кількості важких завдань із вагою з фіксованою кількістю потоків. У кожному із завдань є багато місць, протягом яких воно може вийти із-за винятків.

Я підкласифікував, 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. І чи вдасться він скинутись, якщо ви зателефонуєте на future.get () PS: future.isDone () [Будь ласка, прочитайте справжнє ім'я api] повернеться істинним, навіть коли помилка закінчилася помилково. Тому що завдання робиться по-справжньому.
Джай Пандіт

Відповіді:


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
Дякую, я закінчила використання цього рішення. Крім того, якщо хтось зацікавлений: інші запропонували не підкласифікувати ExecutorService, але я все-таки зробив це, бо хотів відстежувати виконання завдань, а не чекати, коли всі вони завершаться, а потім дзвонять get () на всі повернуті майбутні .
Том

1
Інший підхід до підкласів виконавцем є підклас FutureTask і перевизначити його «Done» метод
NOS

1
Том >> Чи можете ви опублікувати свій зразок фрагменту коду, де ви підкласирували ExecutorService для моніторингу завдань під час їх виконання ...
jagamot

1
Ця відповідь не буде працювати, якщо ви використовуєте ComplableFuture.runAsync, оскільки afterExecute буде містити об'єкт, який є приватним пакетом і не має можливості отримати доступ до переданих елементів. Я обійшов його, завершивши дзвінок. Дивіться мою відповідь нижче.
ммм

2
Чи потрібно перевіряти, чи завершено майбутнє з використанням future.isDone()? Оскільки afterExecuteзапускається після Runnableзавершення, я припускаю, що future.isDone()завжди повертається true.
Searene

248

ПОПЕРЕДЖЕННЯ . Слід зазначити, що це рішення блокує викликову нитку.


Якщо ви хочете опрацювати винятки, кинуті завданням, тоді, як правило, краще використовувати, Callableа не використовувати Runnable.

Callable.call() Дозволяється кидати перевірені винятки, і вони повертаються до виклику:

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

Якщо Callable.call()викинеш виняток, це буде загорнуто у ExecutionExceptionта кинуто Future.get().

Це, ймовірно, буде набагато краще, ніж підкласи ThreadPoolExecutor. Це також дає можливість повторно подати завдання, якщо виняток підлягає відновленню.


5
> Callable.call () дозволено викидати перевірені винятки, і вони повертаються до викличного потоку: Зауважте, що викинуте виняток поширюватиметься на викликовий потік лише у випадку, якщо future.get()або викликається його перевантажена версія.
nhylated

16
Це ідеально, але що робити, якщо я виконую завдання паралельно і не хочу блокувати виконання?
Григорій Кіслін

43
Не використовуйте це рішення, оскільки воно порушує ціль використання ExecutorService. ExecutorService - це асинхронний механізм виконання, який здатний виконувати завдання у фоновому режимі. Якщо ви викликаєте future.get () відразу після його виконання, він заблокує викликовий потік, поки завдання не буде закінчено.
користувач1801374

2
Це рішення не повинно бути настільки високим. Future.get () працює синхронно і буде діяти як блокатор до тих пір, поки Runnable або Callable не будуть виконані і як зазначено вище, не переможуть цілі використання служби Виконавця
Super Hans

2
Як зазначав #nhylated, це заслуговує jdk BUG. Якщо Future.get () не викликається, будь-яке невиконане виключення з Callable мовчки ігнорується. Дуже поганий дизайн .... щойно витратили 1+ днів, щоб з’ясувати, що бібліотека використовувала це, а jdk мовчки ігнорував винятки. І це все ще існує в jdk12.
Бен Цзян

18

Пояснення такої поведінки є правильним у javadoc for afterExecute :

Примітка. Якщо дії вкладені у завдання (наприклад, FutureTask) явно або за допомогою таких методів, як представлення, ці об'єкти завдань вловлюють та підтримують обчислювальні винятки, і тому вони не викликають різкого припинення, і внутрішні винятки не передаються цьому методу .


10

Я обминув це, загорнувши поданий виконувачем поданий виконавець.

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

3
Ви можете покращити читабельність за допомогою whenComplete()методу CompletableFuture.
Едуард Вірх

@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 .

Вам потрібна Дзвінка або Runnable, який реалізує інтерфейс ManagedTask .

Метод getManagedTaskListener повертає потрібний екземпляр.

public ManagedTaskListener getManagedTaskListener() {

І ви реалізуєте в ManagedTaskListener своєtaskDone методу:

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

Детальніше про керований життєвий цикл завдань та слухача .


2

Це працює

  • Він походить від SingleThreadExecutor, але ви можете легко адаптувати його
  • Java-код Lamdas 8, але його легко виправити

Це створить Виконавця з єдиною ниткою, який може отримати багато завдань; і чекатиме завершення поточного виконання, щоб розпочати наступне

У разі помилки або виключення uncaughtExceptionHandler зловить його

публічний заключний клас SingleThreadExecutorWithExceptions {

    public static ExecutorService newSingleThreadExecutorWithExceptions (final Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {

        ThreadFactory factory = (Runnable runnable) -> {
            final Thread newThread = new Thread (запускається, "SingleThreadExecutorWithExceptions");
            newThread.setUncaughtExceptionHandler ((фінальна різьба Thread, остаточна метальна передача) -> {
                uncaughtExceptionHandler.uncaughtException (caugthThread, кидається);
            });
            повернути новийTheread;
        };
        повернути нове FinalizableDelegatedExecutorService
                (новий ThreadPoolExecutor (1, 1,
                        0L, TimeUnit.MILLISECONDS,
                        нова LinkedBlockingQueue (),
                        фабрика) {


                    захищена порожнеча afterExecute (Біг, що можна запустити, кидається) {
                        super.afterExecute (працює, перекидається);
                        if (кидаюча == null && runnable instanceof Future) {
                            спробуйте {
                                Майбутнє майбутнє = (Майбутнє), що працює;
                                якщо (future.isDone ()) {
                                    future.get ();
                                }
                            } улов (CancellationException ce) {
                                перекидний = ce;
                            } улов (ExecutionException ee) {
                                киданий = ee.getCause ();
                            } улов (InterruptException, тобто) {
                                Thread.currentThread (). Interrupt (); // ігнорувати / скидати
                            }
                        }
                        if (кидаюча! = null) {
                            uncaughtExceptionHandler.uncaughtException (Thread.currentThread (), кидається);
                        }
                    }
                });
    }



    приватний статичний клас FinalizableDelegatedExecutorService
            розширює DelegatedExecutorService {
        FinalizableDelegatedExecutorService (виконавець ExecutorService) {
            супер (виконавець);
        }
        захищена недійсна фіналізація () {
            super.shutdown ();
        }
    }

    / **
     * Клас обгортки, який відкриває лише методи ExecutorService
     * реалізації ExecutorService.
     * /
    приватний статичний клас DelegatedExecutorService розширює AbstractExecutorService {
        приватний кінцевий ExecutorService e;
        DelegatedExecutorService (виконавець ExecutorService) {e = виконавець; }
        публічне недійсне виконання (команда Runnable) {e.execute (команда); }
        public void shutdown () {e.shutdown (); }
        загальнодоступний список shutdownNow () {return e.shutdownNow (); }
        public boolean isShutdown () {return e.isShutdown (); }
        public boolean isTerminated () {return e.isTerminated (); }
        публічне булеве очікування випробування (тривалий час очікування, одиниця TimeUnit)
                кидки InterruptedException {
            повернути e.awaitTermina (таймаут, одиниця);
        }
        публічне подання на майбутнє (завдання, яке можна виконати) {
            повернути e.submit (завдання);
        }
        публічне подання у майбутнє (завдання, що викликається) {
            повернути e.submit (завдання);
        }
        публічне подання на майбутнє (виконання завдання, результат T) {
            повернути e.submit (завдання, результат);
        }
        загальнодоступний список> invokeAll (Колекція> завдання)
                кидки InterruptedException {
            повернути e.invokeAll (завдання);
        }
        загальнодоступний список> invokeAll (Колекція> завдання,
                                             довгий час очікування, одиниця TimeUnit)
                кидки InterruptedException {
            повернути e.invokeAll (завдання, тайм-аут, одиниця);
        }
        public T invokeAny (Колекція> завдання)
                кидає InterruptedException, ExecutionException {
            повернути e.invokeAny (завдання);
        }
        public T invokeAny (Колекція> завдання,
                               довгий час очікування, одиниця TimeUnit)
                кидає InterruptException, ExecutionException, TimeoutException {
            повернути e.invokeAny (завдання, тайм-аут, одиниця);
        }
    }



    приватний SingleThreadExecutorWithExceptions () {}
}

Використання finalize, на жаль, трохи нестабільне, оскільки воно буде називатися лише "пізніше, коли збирач сміття збирає його" (або, можливо, не у випадку з Thread, dunno) ...
rogerdpack

1

Якщо ви хочете стежити за виконанням завдання, ви можете обернути 1 або 2 потоки (можливо, більше залежно від завантаження) і використовувати їх для отримання завдань із обгортки ExecutionCompletionService.


0

Якщо ваш ExecutorServiceприхід із зовнішнього джерела (тобто підклас ThreadPoolExecutorта переопределення неможливо afterExecute()), ви можете використовувати динамічний проксі для досягнення бажаної поведінки:

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обертає свій runnableINTO 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 збирає винятки.
Том

5
Метод uncaughtException не викликається, оскільки метод подання ExecutorService обертає Callable / Runnable у майбутньому; виняток там захоплений.
Еміль Сит
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.