Как правильно отловить RuntimeExceptions от исполнителей?

Скажите, что у меня есть следующий код:

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(myRunnable);

Теперь, если myRunnable бросает RuntimeExcpetion, как я могу его поймать? Один из способов - предоставить мою собственную реализацию ThreadFactory для newSingleThreadExecutor() и установить собственные uncaughtExceptionHandler для Thread, которые выходят из нее. Другой способ - обернуть myRunnable в локальный (анонимный) Runnable, содержащий блок try-catch. Возможно, есть и другие подобные обходные пути. Но ... почему-то это кажется грязным, я чувствую, что это не должно быть так сложно. Есть чистое решение?


person Joonas Pulakka    schedule 06.11.2009    source источник
comment
Честно говоря, я сомневаюсь в том, что улавливает исключение, возникшее в другом потоке. Должен ли текущий поток join ожидать возникновения исключения? Вы не упомянули об этом в вопросе.   -  person BalusC    schedule 06.11.2009
comment
@BalusC: Маршалинг исключения из рабочего потока обратно в вызывающий поток является общим требованием многих приложений. Например, приложение пользовательского интерфейса может вызвать поток SwingWorker для выполнения некоторой фоновой обработки. Если обработка завершается неудачно, исключение необходимо передать обратно в поток отправки событий.   -  person Adamski    schedule 06.11.2009
comment
Это обычное требование. Поток 1 генерирует некоторую работу, выполняет ее через поток 2, но должен понимать, удалось это или нет (то есть сгенерировано исключение). Фреймворк Executor поможет вам в этом.   -  person Brian Agnew    schedule 06.11.2009
comment
Ммм, на самом деле я не думал об этом так далеко. Мне просто было любопытно, как вообще подойти к этой проблеме. Но людям, кажется, есть что сказать о submit() и Future ниже :-)   -  person Joonas Pulakka    schedule 06.11.2009


Ответы (5)


Чистый обходной путь - использовать ExecutorService.submit() вместо execute(). Это вернет вам Future, который вы можете использовать для получения результата или исключения задачи:

ExecutorService executor = Executors.newSingleThreadExecutor();
Runnable task = new Runnable() {
  public void run() {
    throw new RuntimeException("foo");
  }
};

Future<?> future = executor.submit(task);
try {
  future.get();
} catch (ExecutionException e) {
  Exception rootException = e.getCause();
}
person skaffman    schedule 06.11.2009
comment
Спасибо, выглядит именно так, как задумывалось. Чистый. - person Joonas Pulakka; 06.11.2009
comment
Кроме того, вы можете использовать Callable, а не Runnable, тогда ваша задача может генерировать отмеченные исключения, а также не отмеченные. - person skaffman; 06.11.2009
comment
getCause возвращает Throwable, а не исключение в 1.6 и 1.7 - person Paul Rubel; 25.07.2013
comment
Проблема в том, что future.get () блокируется, поэтому, если вы хотите иметь возможность запускать свою задачу асинхронно, это не нормально. - person Loic; 20.01.2015
comment
Как прокомментировал @Loic, это решение бесполезно, поскольку оно в первую очередь поражает всю цель использования Executors. - person m0skit0; 21.02.2017
comment
@Loic @ m0skit0 Я не вижу проблемы. Отправьте несколько заданий и сохраните их фьючерсы (этот шаг ведет себя как fork), после того, как все задания были отправлены, вызовите get для сохраненных фьючерсов (если вы get все фьючерсы, этот шаг ведет себя как join ). Недостаток: вы не можете перехватить исключения во время их создания. Вы должны дождаться завершения всех заданий (в худшем случае). Однако OP заботился только о как , но не when для перехвата исключений. - person Socowi; 05.06.2020

Украсьте runnable в другом runnable, который улавливает исключения времени выполнения и обрабатывает их:

public class REHandler implements Runnable {
    Runnable delegate;
    public REHandler (Runnable delegate) {
        this.delegate = delegate;
    }
    public void run () {
        try {
            delegate.run ();
        } catch (RuntimeException e) {
            ... your fancy error handling here ...
        }
    }
}

executor.execute(new REHandler (myRunnable));
person Aaron Digulla    schedule 06.11.2009

Почему бы не вызвать _ 1_, получите Future назад, а затем самостоятельно обрабатывать возможные исключения при вызове _ 3_?

person Brian Agnew    schedule 06.11.2009

Скаффман прав в том, что использование submit - самый чистый подход. Альтернативный подход - создать подкласс ThreadPoolExecutor и переопределить afterExecute(Runnable, Throwable). Если вы последуете этому подходу, обязательно вызовите execute(Runnable), а не submit(Runnable), иначе afterExecute не будет вызван.

Согласно описанию API:

Метод, вызываемый после завершения выполнения данного Runnable. Этот метод вызывается потоком, выполнившим задачу. Если не равно нулю, Throwable - это неперехваченные RuntimeException или Error, которые привели к внезапному прекращению выполнения.

Примечание. Когда действия заключены в задачи (например, FutureTask) явно или с помощью таких методов, как submit, эти объекты задач улавливают и поддерживают вычислительные исключения, поэтому они не вызывают внезапного завершения, а внутренние исключения не передаются. к этому методу.

person Adamski    schedule 06.11.2009

задача (Callable или Runnable), отправленная в ThreadPoolExecutors, будет преобразована в FuturnTask, содержит свойство с именем callable, равное отправляемой вами задаче. FuturnTask имеет свой собственный run метод, как показано ниже. Все исключения или бросаемые в c.call() исключения будут перехвачены и помещены в свойство с именем outcome. При вызове метода get FuturnTask будет выброшено outcome

FuturnTask.run из исходного кода Jdk1.8

public void run() {
        ...
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    // save ex into `outcome` prop
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        }
        ...
    }

если вы хотите поймать исключение:

      1. skaffman's answer
      2. overwrite `afterExecute` when you new a ThreadPoolExecutor
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
            Throwable cause = null;
            if (t == null && r instanceof Future) {
                try {
                    ((Future<?>) r).get();
                } catch (InterruptedException | ExecutionException e) {
                    cause = e;
                }
            } else if (t != null) {
                cause = t;
            }
            if (cause != null) {
                // log error
            }
        }
person gilon chiu    schedule 25.08.2016