计划执行器服务异常处理tl;博士问题解决方案完整的示例代码

2022-08-31 13:34:45

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


答案 1

tl;博士

任何转义方法的异常都会停止所有进一步的工作,恕不另行通知。run

始终在方法中使用 try-catch。如果您希望计划的活动继续进行,请尝试恢复。run

@Override
public void run ()
{
    try {
        doChore();
    } catch ( Exception e ) { 
        logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() );
    }
}

问题

这个问题指的是调度执行器服务的关键技巧:任何到达执行器的抛出异常或错误都会导致执行器停止。不再调用 Runnable,不再做任何工作。这种停工悄悄发生,你不会被告知。这篇淘气的语言博客文章有趣地叙述了了解这种行为的艰难方法。

解决方案

yegor256的答案和arun_suresh的答案似乎都基本正确。这些答案有两个问题:

  • 捕获错误和异常
  • 有点复杂

错误异常 ?

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

捕获错误和异常的一个原因可能涉及在任务中使用库。请参阅jannis的评论

捕获异常和错误的一种方法是捕获它们的超类,例如Storeable

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

老式语法

老式的方式,在lambs之前。

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 如何,在我看来,在 Runnable 的任何运行方法中始终使用通用方法似乎是明智的。对于可调用对象的任何调用方法,同上。try-catch( Exception† e )


完整的示例代码

在实际工作中,我可能会单独定义而不是嵌套。但这确实是一个整洁的多合一示例。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." );

    }
}

运行时。

巴西尔 - 开始。

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

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

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

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

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

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

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

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

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

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

巴西尔 - 完成。

另一个例子

这是另一个示例。在这里,我们的任务意味着运行大约二十次,每五秒钟一次,持续一分钟。但是在第五次运行中,我们抛出了一个异常。

public class App2
{
    public static void main ( String[] args )
    {
        ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
        final AtomicInteger counter = new AtomicInteger( 0 );
        Runnable task = ( ) -> {
            int c = counter.incrementAndGet();
            if ( c > 4 )
            {
                System.out.println( "THROWING EXCEPTION at " + Instant.now() );
                throw new IllegalStateException( "Bogus exception. c = " + c + ". " + Instant.now() ); // Notice how this exception is silently swallowed by the scheduled executor service, while causing a work stoppage.
            }
            System.out.println( "Task running. c = " + c + ". " + Instant.now() );
        };
        ses.scheduleAtFixedRate( task , 0 , 5 , TimeUnit.SECONDS );

        try { Thread.sleep( Duration.ofMinutes( 1 ).toMillis() ); }catch ( InterruptedException e ) { e.printStackTrace(); }
        System.out.println( "Main thread done sleeping. " + Instant.now() );

        ses.shutdown();
        try { ses.awaitTermination( 1 , TimeUnit.MINUTES ); }catch ( InterruptedException e ) { e.printStackTrace(); }
    }
}

运行时。

Task running. c = 1. 2021-10-14T20:09:16.317995Z
Task running. c = 2. 2021-10-14T20:09:21.321536Z
Task running. c = 3. 2021-10-14T20:09:26.318642Z
Task running. c = 4. 2021-10-14T20:09:31.318320Z
THROWING EXCEPTION at 2021-10-14T20:09:36.321458Z
Main thread done sleeping. 2021-10-14T20:10:16.320430Z

通知:

  • 异常被计划的执行程序服务以静默方式吞噬。
  • 发生工作停止。没有安排进一步执行我们的任务。这又是一个无声的问题。

因此,当您的任务引发异常时,您会得到最糟糕的结果:无声停工,没有解释。

如上所述,解决方案:始终在方法中使用 a。try-catchrun


† 或者可能是可抛出的而不是异常来捕获错误对象。


答案 2

你应该使用你返回的对象::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();
}