I use ScheduledExecutorService to execute a method periodically.
p-code:
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); ScheduledFuture<?> handle = scheduler.scheduleWithFixedDelay(new Runnable() { public void run() { //Do business logic, may Exception occurs } }, 1, 10, TimeUnit.SECONDS);
My question:
How to continue the scheduler, if run()
throws Exception? Should I try-catch all Exception in method run()
? Or any built-in callback method to handle the Exception? Thanks!
Methods of ScheduledExecutorServiceSubmits a periodic action that becomes enabled first after the given initial delay, and subsequently with the given period; that is, executions will commence after initialDelay, then initialDelay + period, then initialDelay + 2 * period, and so on.
Simplest Approach: Just Add a Try-Catch Always wrap your Runnable's code in a Try-Catch to catch any and all exceptions and errors.
public interface ScheduledExecutorService extends ExecutorService. An ExecutorService that can schedule commands to run after a given delay, or to execute periodically. The schedule methods create tasks with various delays and return a task object that can be used to cancel or check execution.
Any exception escaping your run
method halts all further work, without notice.
Always use a try-catch
within your run
method. Try to recover if you want scheduled activity to continue.
@Override public void run () { try { doChore(); } catch ( Exception e ) { logger.error( "Caught exception in ScheduledExecutorService. StackTrace:\n" + t.getStackTrace() ); } }
The question refers to the critical trick with a ScheduledExecutorService
: Any thrown exception or error reaching the executor causes the executor to halt. No more invocations on the Runnable, no more work done. This work stoppage happens silently, you'll not be informed. This naughty-language blog posting entertainingly narrates the hard way to learn about this behavior.
The answer by yegor256 and the answer by arun_suresh both seem to be basically correct. Two issues with those answers:
In Java we normally catch only exceptions, not errors. But in this special case of ScheduledExecutorService, failing to catch either will mean a work stoppage. So you may want to catch both. I'm not 100% sure about this, not knowing fully the implications of catching all errors. Please correct me if needed.
One reason to catch errors as well as exceptions might involve the use of libraries within your task. See the comment by jannis.
One way to catch both exceptions and errors is to catch their superclass, Throwable for an example.
} catch ( Throwable t ) {
…rather than…
} catch ( Exception e ) {
Try-Catch
But both answers are a bit complicated. Just for the record, I'll show the simplest solution:
Always wrap your Runnable's code in a Try-Catch to catch any and all exceptions and errors.
With a lambda (in Java 8 and later).
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() ); } };
The old-fashioned way, before 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() ); } } };
Regardless of a ScheduledExecutorService
, it seems sensible to me to always use a general try-catch( Exception† e )
in any run
method of a Runnable
. Ditto for any call
method of a Callable
.
In real work, I would likely define the Runnable
separately rather than nested. But this makes for neat all-in-one example.
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." ); } }
When run.
BASIL - Start.
Now: 2018-04-10T16:46:01.423286-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:03.449178-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:05.450107-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:07.450586-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:09.456076-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:11.456872-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:13.461944-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:15.463837-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:17.469218-07:00[America/Los_Angeles]
Now: 2018-04-10T16:46:19.473935-07:00[America/Los_Angeles]
BASIL - Done.
Here is another example. Here our task is meant to run about twenty times, once every five seconds for a minute. But on the fifth run, we throw an exception.
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(); } } }
When run.
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
Notice:
So when your task throws an exception, you get the worst outcome possible: Silent work stoppage with no explanation.
The solution, as mentioned above: Always use a try-catch
within your run
method.
† Or perhaps Throwable
instead of Exception
to catch Error
objects too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With