web-dev-qa-db-ja.com

ScheduledExecutorService例外処理

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()のすべての例外をキャッチする必要がありますか?または、例外を処理するための組み込みコールバックメソッドはありますか?ありがとう!

scheduler.scheduleWithFixedDelay(...)によって返されるScheduledFutureオブジェクトを次のように使用する必要があります。

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

tl; dr

runメソッドをエスケープする例外は、予告なしにすべての作業を停止します。

常にrunメソッド内で_try-catch_を使用してください。スケジュールされたアクティビティを続行する場合は、回復を試みてください。

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

問題

質問は、 ScheduledExecutorServiceを使用した重要なトリックに関するものです。スローされた例外またはエラーがエグゼキューターに到達すると、エグゼキューターが停止します。Runnableでの呼び出しはもう必要ありません。この作業停止は黙って行われ、通知されません。このいたずらな言語 ブログ投稿 は、この振る舞いについて学ぶための難しい方法を楽しく語っています。

ソリューション

yegor256による回答arun_sureshによる回答 は両方とも基本的に正しいようです。それらの答えに関する2つの問題:

  • エラーと例外をキャッチ
  • 少し複雑

エラーand例外?

Java通常キャッチするのは 例外 であり、 エラー ではありません。ただし、ScheduledExecutorServiceのこの特殊なケースでは、どちらかをキャッチできなければ作業を意味します停止しますので、両方をキャッチすることもできますが、すべてのエラーをキャッチすることの意味を完全に理解していないため、これについては100%確信が持てません。

例外とエラーの両方をキャッチする1つの方法は、スーパークラス Throwable をキャッチすることです。

_} catch ( Throwable t ) {
_

…のではなく…

_} catch ( Exception e ) {
_

最も単純なアプローチ:_Try-Catch_を追加するだけです

しかし、両方の答えは少し複雑です。記録のために、最も簡単な解決策を示します。

Runnableのコードを常にTry-Catchにラップして、すべての例外andエラーをキャッチします。

ラムダ構文

ラムダ付き(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() );
    }
};
_

昔ながらの構文

ラムダ以前の昔ながらの方法。

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

すべてのRunnable/Callableで

ScheduledExecutorService に関係なく、anyで一般的なtry-catch( Exception† e )を常に使用するのが賢明なようです- runRunnable のメソッド。 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 [アメリカ/ Los_Angeles]

現在:2018-04-10T16:46:09.456076-07:00 [America/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 [アメリカ/ロスアンジェレス]

現在: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 オブジェクトもキャッチします。

83
Basil Bourque

@MBecソリューションに触発されて、ScheduledExecutorServiceのNice汎用ラッパーを作成しました。

  • 未処理のスローされた例外をキャッチして出力します。
  • futureの代わりにJava 8 CompletableFutureを返します。

:)

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

}
4
dorony

これは古い質問であることは知っていますが、誰かが遅延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;
}

CompletableFutureの例外の処理:

CompletableFuture<String> delayed = delayed(Duration.ofSeconds(5));
delayed.exceptionally(ex -> {
    //handle exception
    return null;
}).thenAccept(value -> {
    //handle value
});
3
MBec

別の解決策は、Runnableの例外を飲み込むことです。 jcabi-log の便利な VerboseRunnable クラスを使用できます。例:

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
yegor256

古い質問ですが、受け入れられた答えは説明を与えず、貧しい例を提供し、最も賛成の答えはいくつかの点で正しいですが、最後にすべてのRunnable.run()メソッドにcatch例外を追加することをお勧めします。
同意しない理由:

  • きちんとしたものではありません。タスクが独自の例外をキャッチするのは標準ではありません。
  • 堅牢ではありません。新しい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();       
  }

デフォルトの動作の例:タスクの実行のいずれかで問題が発生した場合、スケジューリングを停止します

3回目の実行で例外をスローし、スケジューリングを終了するタスクを実行します。いくつかのシナリオでは、それが必要です。

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

出力:

 get()
 pool-1-thread-1の前、execution 
 pool-1-thread-1、execution 
 pool-1-thread-1 execution 
 exception catch:Java.lang.IllegalArgumentException:ohhh an Exception in MyRunnable 

タスクの実行のいずれかで問題が発生した場合、スケジューリングを続行する可能性がある例

最初の2回の実行で例外をスローし、3回目の実行でエラーをスローするタスクを実行します。タスクのクライアントがスケジューリングを停止するかしないかを選択できることがわかります。ここでは例外の場合に進み、エラーの場合に停止します。

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、実行
例外の場合に再スケジュール
 pool-1-thread-1、実行
例外の場合に再スケジュール
 pool-1-thread-2、実行
エラーの場合に停止します
1
davidxxx

(ScheduledExecutorService)に渡されるスレッドのrun()の例外はスローされず、future.get()を使用してステータスを取得すると、メインスレッドは無限に待機します。

0
Guru