ScheduledExecutorService Exception handling

前端 未结 7 2020
再見小時候
再見小時候 2020-11-28 22:57

I use ScheduledExecutorService to execute a method periodically.

p-code:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecu         


        
相关标签:
7条回答
  • 2020-11-28 23:09

    Old question but the accepted answer doesn't give explanations and provides a poor example and the most upvoted answer is right on some points but finally encourages you to add catch exceptions in every Runnable.run() method.
    I disagree because :

    • it is not neat : not standard for a task to catch its own exceptions.
    • it is not robust : a new Runnable subclass could forget to perform the exception catch and the failover associated.
    • it defeats the low coupling promoted by tasks since that couples the tasks to execute with the way of handling the task result.
    • it mixes responsibilities : that is not the task responsibility to handle the exception or to communicate the exception to the caller. A task is something to execute.

    I think that the exception propagation should be performed by the ExecutorService framework and actually it offers that feature.
    Besides, trying to be too clever by trying to short-circuiting the ExecutorService way of working is not a good idea either : the framework may evolve and you want to use it in a standard way.
    At last, letting the ExecutorService framework to make its job doesn't mean necessarily halting the subsequent invocations task.
    If a scheduled task encounters an issue, that is the caller responsibility to re-schedule or not the task according to the issue cause.
    Each layer has its its responsibilities. Keeping these make code both clear and maintainable.


    ScheduledFuture.get() : the right API to catch exceptions and errors occurred in the task

    ScheduledExecutorService.scheduleWithFixedDelay()/scheduleAtFixRate() state in their specification :

    If any execution of the task encounters an exception, subsequent executions are suppressed. Otherwise, the task will only terminate via cancellation or termination of the executor.

    It means that ScheduledFuture.get() doesn't return at each scheduled invocation but that it returns for the last invocation of the task, that is a task cancelation : caused by ScheduledFuture.cancel() or a exception thrown in the task.
    So handling the ScheduledFuture return to capture the exception with ScheduledFuture.get() looks right :

      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();       
      }
    

    Example with the default behavior : halting the scheduling if one of the task execution encounters an issue

    It executes a task that for the third executions thrown an exception and terminates the scheduling. In some scenarios, we want that.

    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");
          }
        }
      }
    }
    

    Output :

    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
    

    Example with the possibility to go on the scheduling if one of the task execution encounters an issue

    It executes a task that throws an exception at the two first executions and throws an error at the third one. We can see that the client of the tasks can choose to halt or not the scheduling : here I go on in cases of exception and I stop in case of error.

    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");
          }
        }
      }
    }
    

    Output :

    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
    
    0 讨论(0)
  • 2020-11-28 23:12

    Any exception in the run() of a thread which is passed to (ScheduledExecutorService) is never thrown out and if we use future.get() to get status, then the main thread waits infinitely

    0 讨论(0)
  • 2020-11-28 23:19

    Inspired by @MBec solution, I wrote a nice generic wrapper for the ScheduledExecutorService that:

    • will catch and print any unhandled thrown exception.
    • will return a Java 8 CompletableFuture instead of a 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();
        }
    
    }
    
    0 讨论(0)
  • Another solution would be to swallow an exception in the Runnable. You can use a convenient VerboseRunnable class from jcabi-log, for example:

    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
    );
    
    0 讨论(0)
  • 2020-11-28 23:24

    You should use the ScheduledFuture object returned by your scheduler.scheduleWithFixedDelay(...) like so :

    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();
    }
    
    0 讨论(0)
  • 2020-11-28 23:28

    I know that this is old question, but if somebody is using delayed CompletableFuture with ScheduledExecutorService then should handle this in that way:

    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;
    }
    

    and handling exception in CompletableFuture:

    CompletableFuture<String> delayed = delayed(Duration.ofSeconds(5));
    delayed.exceptionally(ex -> {
        //handle exception
        return null;
    }).thenAccept(value -> {
        //handle value
    });
    
    0 讨论(0)
提交回复
热议问题