ScheduledExecutorService异常处理

卢声远*_* 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

TL;博士

任何逃避您的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语法

使用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)

在每个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." );

    }
}
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物品.

  • @JeffreyBlattman (A) 通常有比简单地让应用程序崩溃更好的方法来处理异常。(B) 在这种情况下,您错过了要点:**您的应用程序不会崩溃!** 抛出的异常被计划的执行程序服务吞没,该服务停止计划进一步执行您的任务。所以你可能会得到最糟糕的结果:无声地停工,没有任何解释。因此,我的建议是合理的:“*始终*在您的`run`方法中使用`try-catch`。” (C) 我在答案中添加了另一个代码示例来演示如何吞掉抛出的异常。 (5认同)
  • 这篇博客文章结束了我的一天. (4认同)
  • @JeffreyBlattman你似乎认为我建议异常应该用无操作空大括号`catch (Exception e) {}`来处理。我从没有说过。我确实说过,但您没有听说过的是,从计划的执行程序服务中冒出的异常*不会*导致您的应用程序崩溃。因此,您建议的“让您的应用程序崩溃”的方法在这种情况下不起作用。我什至给了你一个代码示例来演示。尝试一下; 我做到了,我今天写这篇文章是为了在发布之前验证我的观点。尝试一下 - 您的应用*不会**崩溃*。 (4认同)
  • 提供一个真实案例来捕获“Throwable”而不是“Exception”:我刚刚遇到了一个问题,第三方库对服务执行定期健康检查。这个库在底层使用了一个“ScheduledExecutorService”。我一直在从健康检查中获得超时。我阅读了链接的博客文章并发现调度程序可能正在吞下错误。所以我为 `Exception` 添加了一个 try-catch,但它没有帮助。结果证明(经过一周左右的调查)这是一个依赖问题,它抛出了一个“NoSuchMethodError”,它是一个“Error”。 (3认同)
  • @ Down-Voter ...请在投票时留下评论评论. (2认同)

aru*_*esh 33

你应该使用ScheduledFuturescheduler.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)

  • @arun_suresh我不明白.在我看来,你的try-catch会立即运行一次.但是您的计划任务将每10秒重复执行一次.您的代码如何捕获以后执行后抛出的任何异常? (6认同)
  • @巴兹尔是对的。这种方法完全没有意义。可惜这个答案目前仍然被接受。它只会让用户感到困惑,如下所示:/sf/ask/4435623181/。任何读者:请继续阅读下一篇文章,即目前获得最多票数的文章。 (6认同)
  • 我发现这里类似的解释非常有用:http://www.cosmocode.de/en/blog/schoenborn/2009-12/17-uncaught-exceptions-in-scheduled-tasks (5认同)
  • 我想它可以工作,但需要你有一个额外的线程,该线程在 `handle.get()` 上保持阻塞。如果它可以在执行程序服务本身的线程内处理会更好。 (2认同)

dav*_*xxx 7

老问题,但接受的答案没有给出解释,并提供了一个糟糕的例子,最高票的答案在某些方面是正确的,但最终鼓励您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();       
  }
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,执行
出现错误时我会停止


yeg*_*256 6

另一种解决方案是吞掉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)


And*_*son 6

捕获异常并使计划任务保持活动状态的巧妙方法。

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

    @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)


MBe*_*Bec 5

我知道这是个老问题,但如果有人使用延迟CompletableFutureScheduledExecutorService那么应该以这种方式处理:

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)