ScheduledExecutorService异常处理

时间:2011-08-01 05:45:21

标签: java multithreading scheduling

我使用ScheduledExecutorService定期执行方法。

p代码:

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

我的问题:

如果run()抛出异常,如何继续调度程序? 我应该尝试捕获方法run()中的所有异常吗?或者任何内置的回调方法来处理异常?谢谢!

9 个答案:

答案 0 :(得分:79)

TL;博士

任何逃避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上不再有调用,不再完成工作。这项工作停工无声地发生,你不会得到通知。这种顽皮的语言blog posting有趣地叙述了学习这种行为的艰难方式。

解决方案

answer by yegor256answer by arun_suresh似乎基本上都是正确的。这些答案有两个问题:

  • 捕获错误以及异常
  • 有点复杂

错误例外?

在Java中,我们通常只捕获exceptions,而不是errors。但是在ScheduledExecutorService的这种特殊情况下,未能捕获任何一个将意味着停止工作。所以你可能想要抓住两者。我不是100%肯定这一点,不完全了解捕获所有错误的含义。如果需要请纠正我。

捕获异常和错误的一种方法是捕获它们的超类Throwable

} catch ( Throwable t ) {

......而不是......

} catch ( Exception e ) {

最简单的方法:只需添加Try-Catch

但两个答案都有点复杂。只是为了记录,我将展示最简单的解决方案:

  

始终将Runnable的代码包装在Try-Catch中以捕获任何和所有异常错误。

Lambda语法

使用lambda(在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() );
    }
};

老式语法

老式的方式,在lambdas之前。

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如何,我总是在run任何 Runnable方法中使用常规try-catch( Exception† e )似乎是明智的。同样适用于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 - 开始。

     

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

     

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

     

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

     

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

     

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

     

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

     

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

     

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

     

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

     

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

     

BASIL - 完成。


†或者Throwable而不是Exception来抓住Error个对象。

答案 1 :(得分:32)

您应该使用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 :(得分:3)

另一个解决方案是在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
);

答案 3 :(得分:3)

我知道这是一个老问题,但如果有人使用<div class="container"> <div class="row"> <div class="col-md-6">Col 1</div> <div class="col-md-6">Col 2</div> </div> </div> 延迟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

答案 4 :(得分:2)

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

}

答案 5 :(得分:2)

问题老了,但是被接受的答案没有给出解释,并且提供了一个不好的例子,最受支持的答案在某些方面是正确的,但是最后鼓励您在每个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

答案 6 :(得分:2)

一种捕获异常并使计划任务保持活动状态的优雅方法。

首先,定义一个函数式接口。

    @FunctionalInterface
    interface NoSuppressedRunnable extends Runnable {

        @Override
        default void run() {
            try {
                doRun();
            } catch (Exception e) {
                log.error("...", e);
            }
        }


        void doRun();

    }

然后,像这样提交作业。

executorService.scheduleAtFixedRate((NoSuppressedRunnable) () -> {
    // Complier implies that this is an implement of doRun() once you put the cast above
}, 0, 60L, TimeUnit.SECONDS);

答案 7 :(得分:0)

传递给(ScheduledExecutorService)的线程的run()中的任何异常都不会被抛出,如果我们使用future.get()来获取状态,那么主线程无限地等待

答案 8 :(得分:0)

就我个人而言,我不同意这里的所有答案。它们的主要问题是它们以奇怪的口味提供相同的解决方案。相反,您应该做的是创建自己的线程工厂,在正在创建的线程上安装一个未捕获的异常处理程序。例如,这是安装在任何可以自行创建线程的执行器中的 DefaultThreadFactory。可耻的是,它仍然是 Java 11 的私有类,因为我想扩展它而不是将它复制到我的代码库中。下面是它在 Executors.java 文件中的显示方式的片段。

    private static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

如您所见,接口本身是一个处理创建新线程的方法。除了找出创建线程工厂所在的线程组之外,它没有什么神奇之处。有趣的是,线程是作为非守护进程创建的。

创建线程后,您可以调用 setThreadUncaughtExceptionHandler,它接受​​一个处理程序,您应该在该处处理该线程中发生的任何未捕获的异常。默认情况下,它会从你的线程组继承,它有以下内容

    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

默认情况下,它会尝试将处理委托给父线程组(如果存在),然后才测试平台默认未捕获的异常处理程序。通常它没有明确安装。如果您想对不知道这一点的糟糕代码库造成一些真正的损害,您可以通过 Thread#setDefaultUncaughtExceptionHandler 安装一个。别担心,如果运行时有安全管理器,您就不会这样做。

如果您要安装自己的处理程序,将调用该处理程序而不是第一组。

现在,解决您的问题:您如何处理 Executors 中的异常。默认情况下,如果代码无法处理自己的错误,则线程被视为。我认为你应该坚持这一点。未捕获的异常处理程序不会保存您的线程。相反,它将帮助您诊断发生了什么。要进入允许定期执行可运行对象的 ScheduledExecutor 实现,适用相同的规则:如果一次执行失败,则该线程以及本应运行的可运行对象将被终止。

简而言之,处理您自己的错误。我们出于某种原因检查了异常。

但是未经检查的异常呢?

有趣,因为我会犯与其他海报相同的罪过:在 Throwable 上使用 try/catch,但断言这不是 ThreadDeath 错误。如果你得到一个,你必须重新抛出它以确保线程确实死了。