卢声远*_* Lu 53 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 Code Online (Sandbox Code Playgroud)
我的问题:
如果run()
抛出异常,如何继续调度程序?我应该尝试捕获方法中的所有异常run()
吗?或者任何内置的回调方法来处理异常?谢谢!
Bas*_*que 88
任何逃避您的run
方法的异常都会停止所有进一步的工作,恕不另行通知.
始终try-catch
在您的run
方法中使用.如果您希望计划活动继续,请尝试恢复.
@Override
public void run ()
{
try {
doChore();
} catch ( Exception e ) {
logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
}
}
Run Code Online (Sandbox Code Playgroud)
问题涉及以下关键技巧ScheduledExecutorService
:任何抛出异常或到达执行程序的错误都会导致执行程序停止.Runnable上没有更多的调用,没有更多的工作.这项工作停工无声地发生,你不会得到通知.这个顽皮的博客文章有趣地叙述了学习这种行为的艰难方式.
在通过yegor256答案和通过arun_suresh答案都似乎是基本正确.这些答案有两个问题:
在Java中,我们通常只捕获异常,而不是错误.但是在ScheduledExecutorService的这种特殊情况下,未能捕获任何一个将意味着停止工作.所以你可能想要抓住两者.我不是100%肯定这一点,不完全了解捕获所有错误的含义.如果需要请纠正我.
捕获异常和错误的一种方法是捕获它们的超类Throwable.
} catch ( Throwable t ) {
Run Code Online (Sandbox Code Playgroud)
…而不是…
} catch ( Exception e ) {
Run Code Online (Sandbox Code Playgroud)
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() );
}
};
Run Code Online (Sandbox Code Playgroud)
在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() );
}
}
};
Run Code Online (Sandbox Code Playgroud)
无论如何,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." );
}
}
Run Code Online (Sandbox Code Playgroud)
跑步时
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
物品.
aru*_*esh 33
你应该使用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();
}
Run Code Online (Sandbox Code Playgroud)
老问题,但接受的答案没有给出解释,并提供了一个糟糕的例子,最高票的答案在某些方面是正确的,但最终鼓励您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();
}
Run Code Online (Sandbox Code Playgroud)
它执行一个任务,第三次执行时抛出异常并终止调度。在某些情况下,我们想要这样。
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");
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
输出 :
在 get() 之前 pool-1-thread-1,执行 pool-1-thread-1,执行 pool-1-thread-1,执行 捕获异常:java.lang.IllegalArgumentException:哦,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");
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
输出 :
pool-1-thread-1,执行 我会在异常情况下重新安排 pool-1-thread-1,执行 我会在异常情况下重新安排 pool-1-thread-2,执行 出现错误时我会停止
另一种解决方案是吞掉Runnable
. 您可以使用jcabi-logVerboseRunnable
中的一个方便的类,例如:
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
);
Run Code Online (Sandbox Code Playgroud)
捕获异常并使计划任务保持活动状态的巧妙方法。
首先,定义一个函数式接口。
@FunctionalInterface
interface NoSuppressedRunnable extends Runnable {
@Override
default void run() {
try {
doRun();
} catch (Exception e) {
log.error("...", e);
}
}
void doRun();
}
Run Code Online (Sandbox Code Playgroud)
然后,像这样提交工作。
executorService.scheduleAtFixedRate((NoSuppressedRunnable) () -> {
// Complier implies that this is an implement of doRun() once you put the cast above
}, 0, 60L, TimeUnit.SECONDS);
Run Code Online (Sandbox Code Playgroud)
我知道这是个老问题,但如果有人使用延迟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;
}
Run Code Online (Sandbox Code Playgroud)
和处理异常CompletableFuture
:
CompletableFuture<String> delayed = delayed(Duration.ofSeconds(5));
delayed.exceptionally(ex -> {
//handle exception
return null;
}).thenAccept(value -> {
//handle value
});
Run Code Online (Sandbox Code Playgroud)