Обработка контрольных данных ScheduledExecutorService

Я использую ScheduledExecutorService для периодического выполнения метода.

р-код:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> handle =
        scheduler.scheduleWithFixedDelay(new Runnable() {
             public void run() { 
                 //Do business logic, may Exception occurs
             }
        }, 1, 10, TimeUnit.SECONDS);

Мой вопрос:

Как продолжить планировщик, если run() throws Exception? Должен ли я попробовать все исключение в методе run()? Или любой встроенный метод обратного вызова для обработки Исключения? Спасибо!

Ответ 1

Вы должны использовать объект ScheduledFuture, который был возвращен вашим scheduler.scheduleWithFixedDelay(...) следующим образом:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> handle =
        scheduler.scheduleWithFixedDelay(new Runnable() {
             public void run() { 
                 throw new RuntimeException("foo");
             }
        }, 1, 10, TimeUnit.SECONDS);

// Create and Start an exception handler thread
// pass the "handle" object to the thread
// Inside the handler thread do :
....
try {
  handle.get();
} catch (ExecutionException e) {
  Exception rootException = e.getCause();
}

Ответ 2

ТЛ; др

Любое исключение, выходящее из вашего метода run , останавливает всю дальнейшую работу без предварительного уведомления.

Всегда используйте try-catch в вашем методе run. Попробуйте восстановить, если вы хотите продолжить запланированные действия.

@Override
public void run ()
{
    try {
        doChore();
    } catch ( Exception e ) { 
        logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
    }
}

Проблема

Этот вопрос относится к критической уловке с ScheduledExecutorService: Любое выброшенное исключение или ошибка, достигающая исполнителя, приводит к его остановке. Больше никаких вызовов в Runnable, больше не выполняется работа, Эта остановка работы происходит тихо, вы не будете проинформированы. Это непослушное сообщение в блоге забавно рассказывает о сложном способе узнать об этом поведении.

Решение

Ответ от yegor256 и ответ от arun_suresh кажутся в основном правильными. Два вопроса с этими ответами:

  • Поймать ошибки, а также исключения
  • Немного сложнее

Ошибки и исключения?

В Java мы обычно ловим только исключения, а не ошибки. Но в этом особом случае ScheduledExecutorService отказ от перехвата будет означать остановку работы. Таким образом, вы можете поймать оба. Я не уверен на 100% в этом, не зная полностью последствий всех ошибок. Пожалуйста, исправьте меня, если необходимо.

Один из способов отловить как исключения, так и ошибки - перехватить их суперкласс, Throwable.

} catch ( Throwable t ) {

... а не...

} catch ( Exception e ) {

Самый простой подход: просто добавьте Try-Catch

Но оба ответа немного сложны. Для справки, я покажу самое простое решение:

Всегда оборачивайте свой Runnable код в Try-Catch, чтобы перехватить все исключения и ошибки.

Лямбда-синтаксис

С лямбдой (в Java 8 и более поздних версиях).

final Runnable someChoreRunnable = () -> {
    try {
        doChore();
    } catch ( Throwable t ) {  // Catch Throwable rather than Exception (a subclass).
        logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
    }
};

Старомодный синтаксис

Старомодный способ, перед лямбдами.

final Runnable someChoreRunnable = new Runnable()
{
    @Override
    public void run ()
    {
        try {
            doChore();
        } catch ( Throwable t ) {  // Catch Throwable rather than Exception (a subclass).
            logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
        }
    }
};

В каждом Runnable/Callable

Независимо от ScheduledExecutorService, мне кажется разумным всегда использовать общий try-catch( Exception† e ) в любом run методе Runnable. То же самое для любого call метода Callable.


Полный пример кода

В реальной работе я бы скорее определил Runnable отдельно, чем во вложении. Но это хороший пример все в одном.

package com.basilbourque.example;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 *  Demo 'ScheduledExecutorService'
 */
public class App {
    public static void main ( String[] args ) {
        App app = new App();
        app.doIt();
    }

    private void doIt () {

        // Demonstrate a working scheduled executor service.
        // Run, and watch the console for 20 seconds.
        System.out.println( "BASIL - Start." );

        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
        ScheduledFuture < ? > handle =
                scheduler.scheduleWithFixedDelay( new Runnable() {
                    public void run () {
                        try {
                            // doChore ;   // Do business logic.
                            System.out.println( "Now: " + ZonedDateTime.now( ZoneId.systemDefault() ) );  // Report current moment.
                        } catch ( Exception e ) {
                            // … handle exception/error. Trap any unexpected exception here rather to stop it reaching and shutting-down the scheduled executor service.
                            // logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + e.getStackTrace() );
                        }   // End of try-catch.
                    }   // End of 'run' method.
                } , 0 , 2 , TimeUnit.SECONDS );


        // Wait a long moment, for background thread to do some work.
        try {
            Thread.sleep( TimeUnit.SECONDS.toMillis( 20 ) );
        } catch ( InterruptedException e ) {
            e.printStackTrace();
        }

        // Time is up. Kill the executor service and its thread pool.
        scheduler.shutdown();

        System.out.println( "BASIL - Done." );

    }
}

При запуске.

BASIL - Start.

Now: 2018-04-10T16:46:01.423286-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:03.449178-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:05.450107-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:07.450586-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:09.456076-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:11.456872-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:13.461944-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:15.463837-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:17.469218-07:00[America/Los_Angeles]

Now: 2018-04-10T16:46:19.473935-07:00[America/Los_Angeles]

BASIL - Done.


† Или, может быть, Throwable вместо Exception, чтобы ловить Error объекты тоже.

Ответ 3

Вдохновленный решением @MBec, я написал хорошую универсальную оболочку для ScheduledExecutorService, которая:

  • перехватит и распечатает любое необработанное исключение.
  • вернет Java 8 CompletableFuture вместо Future.

:)

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * This class use as a wrapper for the Native Java ScheduledExecutorService class.
 * It was created in order to address the very unpleasant scenario of silent death!
 * explanation: each time an unhandled exception get thrown from a running task that runs by ScheduledExecutorService
 * the thread will die and the exception will die with it (nothing will propagate back to the main thread).
 *
 * However, HonestScheduledExecutorService will gracefully print the thrown exception with a custom/default message,
 * and will also return a Java 8 compliant CompletableFuture for your convenience :)
 */
@Slf4j
public class HonestScheduledExecutorService {

    private final ScheduledExecutorService scheduledExecutorService;
    private static final String DEFAULT_FAILURE_MSG = "Failure occurred when running scheduled task.";

    HonestScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
        this.scheduledExecutorService = scheduledExecutorService;
    }

    public CompletableFuture<Object> scheduleWithFixedDelay(Callable callable, String onFailureMsg, long initialDelay, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(onFailureMsg) ? DEFAULT_FAILURE_MSG : onFailureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, delay, unit);

        return delayed;
    }

    public CompletableFuture<Void> scheduleWithFixedDelay(Runnable runnable, String onFailureMsg, long initialDelay, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(onFailureMsg) ? DEFAULT_FAILURE_MSG : onFailureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, delay, unit);

        return delayed;
    }

    public CompletableFuture<Object> schedule(Callable callable, String failureMsg, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.schedule(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, delay, unit);

        return delayed;
    }

    public CompletableFuture<Void> schedule(Runnable runnable, String failureMsg, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.schedule(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, delay, unit);

        return delayed;
    }

    public CompletableFuture<Object> scheduleAtFixedRate(Callable callable, String failureMsg, long initialDelay, long period, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, period, unit);

        return delayed;
    }

    public CompletableFuture<Void> scheduleAtFixedRate(Runnable runnable, String failureMsg, long initialDelay, long period, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, period, unit);

        return delayed;
    }

    public CompletableFuture<Object> execute(Callable callable, String failureMsg) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Object> delayed = new CompletableFuture<>();

        scheduledExecutorService.execute(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        });

        return delayed;
    }

    public CompletableFuture<Void> execute(Runnable runnable, String failureMsg) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture<Void> delayed = new CompletableFuture<>();

        scheduledExecutorService.execute(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        });

        return delayed;
    }

    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
        return scheduledExecutorService.awaitTermination(timeout, unit);
    }

    public List<Runnable> shutdownNow() {
        return scheduledExecutorService.shutdownNow();
    }

    public void shutdown() {
        scheduledExecutorService.shutdown();
    }

}

Ответ 4

Другим решением было бы усвоить исключение в Runnable. Вы можете использовать удобный VerboseRunnable класс из jcabi-log, например:

import com.jcabi.log.VerboseRunnable;
scheduler.scheduleWithFixedDelay(
  new VerboseRunnable(
    Runnable() {
      public void run() { 
        // do business logic, may Exception occurs
      }
    },
    true // it means that all exceptions will be swallowed and logged
  ),
  1, 10, TimeUnit.SECONDS
);

Ответ 5

Я знаю, что это старый вопрос, но если кто-то использует задержку CompletableFuture с ScheduledExecutorService, тогда он должен обрабатывать это следующим образом:

private static CompletableFuture<String> delayed(Duration delay) {
    CompletableFuture<String> delayed = new CompletableFuture<>();
    executor.schedule(() -> {
        String value = null;
        try {
            value = mayThrowExceptionOrValue();
        } catch (Throwable ex) {
            delayed.completeExceptionally(ex);
        }
        if (!delayed.isCompletedExceptionally()) {
            delayed.complete(value);
        }
    }, delay.toMillis(), TimeUnit.MILLISECONDS);
    return delayed;
}

и исключение обработки в CompletableFuture:

CompletableFuture<String> delayed = delayed(Duration.ofSeconds(5));
delayed.exceptionally(ex -> {
    //handle exception
    return null;
}).thenAccept(value -> {
    //handle value
});

Ответ 6

Старый вопрос, но принятый ответ не дает объяснений и дает плохой пример, и наиболее одобренный ответ является правильным в некоторых моментах, но в конечном итоге призывает вас добавлять исключения catch в каждый метод Runnable.run().
Я не согласен, потому что:

  • это не опрятно: не стандартно для задачи поймать ее собственные исключения.
  • это не надежно: новый подкласс Runnable может забыть выполнить исключение и связанный с ним отказоустойчивый.
  • это побеждает низкую связь, продвигаемую задачами, так как это связывает задачи для выполнения с способом обработки результата задачи.
  • это смешивает обязанности: это не является задачей задачи обработать исключение или сообщить об исключении вызывающей стороне. Задача - это то, что нужно выполнить.

Я думаю, что распространение исключений должно выполняться платформой ExecutorService, и на самом деле она предлагает эту функцию.
Кроме того, пытаться быть слишком умным, пытаясь замкнуть схему работы ExecutorService, тоже не очень хорошая идея: структура может развиваться, и вы хотите использовать ее стандартным способом.
Наконец, позволить каркасу ExecutorService выполнить свою работу не означает обязательной остановки последующей задачи вызовов.
Если запланированная задача сталкивается с проблемой, то ответственность за ее перепланирование или нет зависит от причины проблемы.
Каждый слой имеет свои обязанности. Их хранение делает код понятным и понятным.


ScheduledFuture.get(): правильный API для перехвата исключений и ошибок, возникших в задаче

Состояние ScheduledExecutorService.scheduleWithFixedDelay()/scheduleAtFixRate() в их спецификации:

Если при выполнении какого-либо задания возникает исключение, последующее казни подавлены. В противном случае задача будет завершена только через отмена или увольнение исполнителя.

Это означает, что ScheduledFuture.get() не возвращается при каждом запланированном вызове, но возвращается для последнего вызова задачи, то есть отмены задачи: вызванной ScheduledFuture.cancel() или исключением, выданным в задаче.
Поэтому обработка возврата ScheduledFuture для захвата исключения с помощью ScheduledFuture.get() выглядит правильно:

  try {
    future.get();

  } catch (InterruptedException e) {
    // ... to handle
  } catch (ExecutionException e) {
    // ... and unwrap the exception OR the error that caused the issue
    Throwable cause = e.getCause();       
  }

Пример с поведением по умолчанию: остановка планирования, если при выполнении одной из задач возникает проблема

Он выполняет задачу, которая для третьего выполнения выдает исключение и прекращает планирование. В некоторых случаях мы хотим этого.

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ScheduledExecutorServiceWithException {

  public static void main(String[] args) {
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

    // variable used to thrown an error at the 3rd task invocation
    AtomicInteger countBeforeError = new AtomicInteger(3);

    // boolean allowing to leave the client to halt the scheduling task or not after a failure
    Future<?> futureA = executor
        .scheduleWithFixedDelay(new MyRunnable(countBeforeError), 1, 2, TimeUnit.SECONDS);
    try {
      System.out.println("before get()");
      futureA.get(); // will return only if canceled
      System.out.println("after get()");
    } catch (InterruptedException e) {
      // handle that : halt or no
    } catch (ExecutionException e) {
      System.out.println("exception caught :" + e.getCause());
    }

    // shutdown the executorservice
    executor.shutdown();
  }

  private static class MyRunnable implements Runnable {

    private final AtomicInteger invocationDone;

    public MyRunnable(AtomicInteger invocationDone) {
      this.invocationDone = invocationDone;
    }

    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName() + ", execution");
      if (invocationDone.decrementAndGet() == 0) {
        throw new IllegalArgumentException("ohhh an Exception in MyRunnable");
      }
    }
  }
}

Вывод:

before get()
pool-1-thread-1, execution
pool-1-thread-1, execution
pool-1-thread-1, execution
exception caught :java.lang.IllegalArgumentException: ohhh an Exception in MyRunnable

Пример с возможностью перейти к планированию, если при выполнении одной из задач возникает проблема

Он выполняет задачу, которая выдает исключение при двух первых выполнениях, и выдает ошибку при третьем. Мы можем видеть, что клиент задач может выбрать, останавливать или нет планирование: здесь я продолжаю в случаях исключения и останавливаюсь в случае ошибки.

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ScheduledExecutorServiceWithException {

  public static void main(String[] args) {
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

    // variable used to thrown an error at the 3rd task invocation
    AtomicInteger countBeforeError = new AtomicInteger(3);

    // boolean allowing to leave the client to halt the scheduling task or not after a failure
    boolean mustHalt = true;
    do {
      Future<?> futureA = executor
              .scheduleWithFixedDelay(new MyRunnable(countBeforeError), 1, 2, TimeUnit.SECONDS);
      try {
        futureA.get(); // will return only if canceled
      } catch (InterruptedException e) {
        // handle that : halt or not halt
      } catch (ExecutionException e) {
        if (e.getCause() instanceof Error) {
          System.out.println("I halt in case of Error");
          mustHalt = true;
        } else {
          System.out.println("I reschedule in case of Exception");
          mustHalt = false;
        }
      }
    }
    while (!mustHalt);
    // shutdown the executorservice
    executor.shutdown();
  }

  private static class MyRunnable implements Runnable {

    private final AtomicInteger invocationDone;

    public MyRunnable(AtomicInteger invocationDone) {
      this.invocationDone = invocationDone;
    }

    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName() + ", execution");

      if (invocationDone.decrementAndGet() == 0) {
        throw new Error("ohhh an Error in MyRunnable");
      } else {
        throw new IllegalArgumentException("ohhh an Exception in MyRunnable");
      }
    }
  }
}

Вывод:

pool-1-thread-1, execution
I reschedule in case of Exception
pool-1-thread-1, execution
I reschedule in case of Exception
pool-1-thread-2, execution
I halt in case of Error

Ответ 7

Любое исключение в run() потока, который передается (ScheduledExecutorService), никогда не выбрасывается, и если мы используем future.get() для получения статуса, то основной поток ждет бесконечно