我使用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()
中的所有异常吗?或者任何内置的回调方法来处理异常?谢谢!
答案 0 :(得分:79)
任何逃避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 yegor256和answer by arun_suresh似乎基本上都是正确的。这些答案有两个问题:
在Java中,我们通常只捕获exceptions,而不是errors。但是在ScheduledExecutorService的这种特殊情况下,未能捕获任何一个将意味着停止工作。所以你可能想要抓住两者。我不是100%肯定这一点,不完全了解捕获所有错误的含义。如果需要请纠正我。
捕获异常和错误的一种方法是捕获它们的超类Throwable。
} catch ( Throwable t ) {
......而不是......
} catch ( Exception e ) {
Try-Catch
但两个答案都有点复杂。只是为了记录,我将展示最简单的解决方案:
始终将Runnable的代码包装在Try-Catch中以捕获任何和所有异常和错误。
使用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() );
}
}
};
无论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 - 完成。
答案 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编写了一个很好的通用包装器:
:)
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()
例外。
我不同意是因为:
我认为异常传播应该由ExecutorService
框架执行,并且实际上它提供了该功能。
此外,尝试通过缩短ExecutorService
的工作方式来变得太聪明也不是一个好主意:框架可能会发展,并且您希望以标准方式使用它。
最后,让ExecutorService
框架开始工作并不一定意味着必须停止后续的调用任务。
如果计划任务遇到问题,则呼叫者有责任根据问题原因重新计划或不重新计划任务。
每层都有其责任。保持这些使代码既清晰又可维护。
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
错误。如果你得到一个,你必须重新抛出它以确保线程确实死了。