Java ExecutorServiceタスクからの例外の処理


213

JavaのThreadPoolExecutorクラスを使用して、多数の重いタスクを一定数のスレッドで実行しようとしています。各タスクには、例外のために失敗する可能性のある多くの場所があります。

サブクラス化ThreadPoolExecutorafterExecute、タスクの実行中に発生したキャッチされない例外を提供することになっているメソッドをオーバーライドしました。しかし、私はそれを機能させることができないようです。

例えば:

public class ThreadPoolErrors extends ThreadPoolExecutor {
    public ThreadPoolErrors() {
        super(  1, // core threads
                1, // max threads
                1, // timeout
                TimeUnit.MINUTES, // timeout units
                new LinkedBlockingQueue<Runnable>() // work queue
        );
    }

    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if(t != null) {
            System.out.println("Got an error: " + t);
        } else {
            System.out.println("Everything's fine--situation normal!");
        }
    }

    public static void main( String [] args) {
        ThreadPoolErrors threadPool = new ThreadPoolErrors();
        threadPool.submit( 
                new Runnable() {
                    public void run() {
                        throw new RuntimeException("Ouch! Got an error.");
                    }
                }
        );
        threadPool.shutdown();
    }
}

このプログラムからの出力は、「すべてが正常です-状況は正常です!」スレッドプールに送信された唯一のRunnableは例外をスローします。ここで何が起こっているかについての手がかりはありますか?

ありがとう!


あなたはタスクの未来、そこで何が起こったのかを決して問いませんでした。サービス実行プログラムまたはプログラム全体がクラッシュすることはありません。例外がキャッチされ、ExecutionExceptionの下にラップされます。そして、future.get()を呼び出すと、彼は再スローされます。PS:実行可能ファイルが誤って終了した場合でも、future.isDone()[実際のAPI名をお読みください]はtrueを返します。タスクが実際に行われるためです。
Jai Pandit

回答:


156

ドキュメントから:

注:アクションが明示的にまたは送信などのメソッドを介してタスク(FutureTaskなど)に囲まれている場合、これらのタスクオブジェクトは計算例外をキャッチして維持するため、突然終了することはなく、内部例外はこのメソッドに渡されません。

Runnableを送信すると、Futureにラップされます。

afterExecuteは次のようになります。

public final class ExtendedExecutor extends ThreadPoolExecutor {

    // ...

    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if (t == null && r instanceof Future<?>) {
            try {
                Future<?> future = (Future<?>) r;
                if (future.isDone()) {
                    future.get();
                }
            } catch (CancellationException ce) {
                t = ce;
            } catch (ExecutionException ee) {
                t = ee.getCause();
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
        }
        if (t != null) {
            System.out.println(t);
        }
    }
}

7
おかげで、私はこのソリューションを使用することになりました。さらに、誰かが興味を持っている場合:他の人がExecutorServiceをサブクラス化しないことを提案しましたが、いずれにしても、すべてのタスクが終了するのを待ってから返されたすべてのFutureでget()を呼び出すのではなく、タスクの完了を監視したかったからです。 。
トム

1
エグゼキューターをサブクラス化する別のアプローチは、FutureTaskをサブクラス化し、その 'done'メソッドをオーバーライドすることです
nos

1
トム>>サンプルスニペットコードを投稿して、ExecutorServiceをサブクラス化してタスクの完了を監視できますか?
jagamot

1
この回答は、ComplableFuture.runAsyncを使用している場合は機能しません。afterExecuteにはパッケージプライベートのオブジェクトが含まれ、スロー可能オブジェクトにアクセスする方法がないためです。通話を折り返すことで回避しました。以下の私の答えを参照してください。
mmm

2
を使用して未来が完了しているfuture.isDone()かどうかを確認する必要がありますか?は完了afterExecute後に実行されるためRunnablefuture.isDone()常に戻ると想定していますtrue
Searene

248

警告:このソリューションは呼び出しスレッドをブロックすることに注意してください。


タスクによってスローされた例外を処理する場合Callableは、を使用するよりも一般的に使用する方が適切ですRunnable

Callable.call() チェックされた例外をスローすることが許可されており、これらは呼び出しスレッドに伝播されます。

Callable task = ...
Future future = executor.submit(task);
try {
   future.get();
} catch (ExecutionException ex) {
   ex.getCause().printStackTrace();
}

Callable.call()例外をスローする場合、これはにラップされ、ExecutionExceptionによってスローされFuture.get()ます。

これはサブクラス化よりもはるかに望ましい方法ThreadPoolExecutorです。また、例外が回復可能なものである場合は、タスクを再送信することもできます。


5
> Callable.call()はチェック済み例外のスローを許可されており、これらは呼び出しスレッド に伝播されます。スローされた例外は、future.get()またはそのオーバーロードされたバージョンが呼び出された場合にのみ、呼び出しスレッドに伝播されることに注意してください。
ニール化2014

16
完璧ですが、タスクを並行して実行し、実行をブロックしたくない場合はどうすればよいですか?
Grigory Kislin

43
このソリューションを使用しないでください。ExecutorServiceを使用する全体の目的が損なわれるためです。ExecutorServiceは、タスクをバックグラウンドで実行できる非同期実行メカニズムです。実行直後にfuture.get()を呼び出すと、タスクが完了するまで呼び出しスレッドがブロックされます。
user1801374

2
このソリューションはそれほど高く評価されるべきではありません。Future.get()は同期的に機能し、RunnableまたはCallableが実行され、上記のように、Executor Serviceを使用する目的を無効にするまでブロッカーとして機能します
Super Hans

2
#nhylatedが指摘したように、これはjdkバグに値します。Future.get()が呼び出されない場合、Callableからのキャッチされない例外は通知なく無視されます。非常に悪い設計....これを使用したライブラリを見つけるために1日以上費やしただけで、jdkは警告なしで例外を無視しました。そして、これはまだjdk12に存在します。
ベン・江

18

この動作の説明は、JavaドキュメントのafterExecuteにあります。

注:アクションが明示的にまたは送信などのメソッドを介してタスク(FutureTaskなど)に囲まれている場合、これらのタスクオブジェクトは計算例外をキャッチして維持するため、突然終了することはなく、内部例外はこのメソッドに渡されません。


10

エグゼキューターに送信された付属のランナブルをラップすることで回避しました。

CompletableFuture.runAsync(() -> {
        try {
              runnable.run();
        } catch (Throwable e) {
              Log.info(Concurrency.class, "runAsync", e);
        }
}, executorService);

3
whenComplete()メソッドを使用すると、読みやすさが向上しCompletableFutureます。
Eduard Wirch、2018

@EduardWirchこれは機能しますが、whenComplete()から例外をスローすることはできません
Akshat

7

私はすべての例外を飲み込んでログに記録VerboseRunnableするjcabi-logのクラスを使用しています。たとえば、非常に便利です。

import com.jcabi.log.VerboseRunnable;
scheduler.scheduleWithFixedDelay(
  new VerboseRunnable(
    Runnable() {
      public void run() { 
        // the code, which may throw
      }
    },
    true // it means that all exceptions will be swallowed and logged
  ),
  1, 1, TimeUnit.MILLISECONDS
);

3

別のソリューションは、ManagedTaskおよびManagedTaskListenerを使用することです

インターフェースManagedTaskを実装するCallableまたはRunnableが必要です

このメソッドgetManagedTaskListenerは、必要なインスタンスを返します。

public ManagedTaskListener getManagedTaskListener() {

そして、ManagedTaskListenertaskDoneメソッドを実装します。

@Override
public void taskDone(Future<?> future, ManagedExecutorService executor, Object task, Throwable exception) {
    if (exception != null) {
        LOGGER.log(Level.SEVERE, exception.getMessage());
    }
}

管理タスクのライフサイクルとリスナーの詳細。


2

これは機能します

  • SingleThreadExecutorから派生していますが、簡単に適応できます
  • Java 8ラムダコードですが、修正は簡単です

これは、多くのタスクを取得できる単一スレッドのエグゼキューターを作成します。現在の実行が終了して次の実行が始まるのを待ちます

uncaugthエラーまたは例外の場合、uncaughtExceptionHandlerがキャッチします

パブリック最終クラスSingleThreadExecutorWithExceptions {

    public static ExecutorService newSingleThreadExecutorWithExceptions(final Thread.UncaughtExceptionHandler uncaughtExceptionHandler){

        ThreadFactory factory =(Runnable runnable)-> {
            final Thread newThread = new Thread(runnable、 "SingleThreadExecutorWithExceptions");
            newThread.setUncaughtExceptionHandler((最終スレッドcaugthThread、最終スロー可能スロー可能)-> {
                uncaughtExceptionHandler.uncaughtException(caugthThread、throwable);
            });
            newThreadを返します。
        };
        新しいFinalizableDelegatedExecutorServiceを返す
                (新しいThreadPoolExecutor(1、1、
                        0L、TimeUnit.MILLISECONDS、
                        新しいLinkedBlockingQueue()、
                        工場){


                    protected void afterExecute(Runnable runnable、Throwable throwable){
                        super.afterExecute(実行可能、スロー可能);
                        if(throwable == null && runnable instanceof Future){
                            {を試す
                                Future future =(Future)runnable;
                                if(future.isDone()){
                                    future.get();
                                }
                            }キャッチ(CancellationException ce){
                                スロー可能= ce;
                            }キャッチ(ExecutionException ee){
                                throwable = ee.getCause();
                            } catch(InterruptedException ie){
                                Thread.currentThread()。interrupt(); //無視/リセット
                            }
                        }
                        if(throwable!= null){
                            uncaughtExceptionHandler.uncaughtException(Thread.currentThread()、throwable);
                        }
                    }
                });
    }



    プライベート静的クラスFinalizableDelegatedExecutorService
            DelegatedExecutorService {
        FinalizableDelegatedExecutorService(ExecutorService executor){
            super(executor);
        }
        protected void finalize(){
            super.shutdown();
        }
    }

    / **
     * ExecutorServiceメソッドのみを公開するラッパークラス
     * ExecutorService実装の。
     * /
    プライベート静的クラスDelegatedExecutorServiceはAbstractExecutorService {
        最終的なプライベートExecutorService e;
        DelegatedExecutorService(ExecutorService executor){e = executor; }
        public void execute(Runnable command){e.execute(command); }
        public void shutdown(){e.shutdown(); }
        public List shutdownNow(){return e.shutdownNow(); }
        public boolean isShutdown(){return e.isShutdown(); }
        public boolean isTerminated(){return e.isTerminated(); }
        public boolean awaitTermination(長いタイムアウト、TimeUnitユニット)
                InterruptedException {をスローします
            e.awaitTermination(timeout、unit);を返します。
        }
        public Future submit(実行可能なタスク){
            e.submit(task)を返します。
        }
        public Future submit(呼び出し可能なタスク){
            e.submit(task)を返します。
        }
        public Future submit(実行可能なタスク、T結果){
            e.submit(タスク、結果);
        }
        公開リスト> invokeAll(コレクション>タスク)
                InterruptedException {をスローします
            e.invokeAll(tasks);を返します。
        }
        public List> invokeAll(Collection> tasks、
                                             長いタイムアウト、TimeUnit単位)
                InterruptedException {をスローします
            e.invokeAll(tasks、timeout、unit);を返します。
        }
        パブリックT invokeAny(Collection> tasks)
                InterruptedException、ExecutionException {をスローします
            e.invokeAny(tasks);を返します。
        }
        public T invokeAny(Collection> tasks、
                               長いタイムアウト、TimeUnit単位)
                InterruptedException、ExecutionException、TimeoutException {をスローします。
            e.invokeAny(tasks、timeout、unit);を返します。
        }
    }



    プライベートSingleThreadExecutorWithExceptions(){}
}

finalizeの使用は、 "ガベージコレクターが収集した後で"呼び出されるため(または、スレッドの場合は
知ら

1

タスクの実行を監視する場合は、1つまたは2つのスレッドをスピンし(おそらく負荷に応じて)、それらを使用してExecutionCompletionServiceラッパーからタスクを取得します。


0

ExecutorService外部ソースからのものである場合(つまり、サブクラス化することができない場合)ThreadPoolExecutorしてオーバーライドafterExecute()することができ場合)、動的プロキシを使用して目的の動作を実現できます。

public static ExecutorService errorAware(final ExecutorService executor) {
    return (ExecutorService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
            new Class[] {ExecutorService.class},
            (proxy, method, args) -> {
                if (method.getName().equals("submit")) {
                    final Object arg0 = args[0];
                    if (arg0 instanceof Runnable) {
                        args[0] = new Runnable() {
                            @Override
                            public void run() {
                                final Runnable task = (Runnable) arg0;
                                try {
                                    task.run();
                                    if (task instanceof Future<?>) {
                                        final Future<?> future = (Future<?>) task;

                                        if (future.isDone()) {
                                            try {
                                                future.get();
                                            } catch (final CancellationException ce) {
                                                // Your error-handling code here
                                                ce.printStackTrace();
                                            } catch (final ExecutionException ee) {
                                                // Your error-handling code here
                                                ee.getCause().printStackTrace();
                                            } catch (final InterruptedException ie) {
                                                Thread.currentThread().interrupt();
                                            }
                                        }
                                    }
                                } catch (final RuntimeException re) {
                                    // Your error-handling code here
                                    re.printStackTrace();
                                    throw re;
                                } catch (final Error e) {
                                    // Your error-handling code here
                                    e.printStackTrace();
                                    throw e;
                                }
                            }
                        };
                    } else if (arg0 instanceof Callable<?>) {
                        args[0] = new Callable<Object>() {
                            @Override
                            public Object call() throws Exception {
                                final Callable<?> task = (Callable<?>) arg0;
                                try {
                                    return task.call();
                                } catch (final Exception e) {
                                    // Your error-handling code here
                                    e.printStackTrace();
                                    throw e;
                                } catch (final Error e) {
                                    // Your error-handling code here
                                    e.printStackTrace();
                                    throw e;
                                }
                            }
                        };
                    }
                }
                return method.invoke(executor, args);
            });
}

0

これは、以下のようにAbstractExecutorService :: submitあなたrunnableRunnableFuture(何もFutureTask)にラップしているためです

AbstractExecutorService.java

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null); /////////HERE////////
    execute(ftask);
    return ftask;
}

その後executeにそれを渡しますWorkerWorker.run()、以下の呼び出します。

ThreadPoolExecutor.java

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();           /////////HERE////////
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

最後task.run();に、上記のコード呼び出しではが呼び出されます FutureTask.run()。例外ハンドラコードは次のとおりです。このため、予期した例外が発生しません。

class FutureTask<V> implements RunnableFuture<V>

public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {   /////////HERE////////
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

0

これはmmmのソリューションに似ていますが、少しわかりやすいです。run()メソッドをラップする抽象クラスをタスクで拡張します。

public abstract Task implements Runnable {

    public abstract void execute();

    public void run() {
      try {
        execute();
      } catch (Throwable t) {
        // handle it  
      }
    }
}


public MySampleTask extends Task {
    public void execute() {
        // heavy, error-prone code here
    }
}

-4

ThreadPoolExecutorをサブクラス化する代わりに、新しいスレッドを作成してUncaughtExceptionHandlerを提供するThreadFactoryインスタンスを提供します


3
私もこれを試しましたが、uncaughtExceptionメソッドが呼び出されないようです。これは、ThreadPoolExecutorクラスのワーカースレッドが例外をキャッチしているためと考えられます。
トム

5
ExecutorServiceのsubmitメソッドがCallable / RunnableをFutureでラップしているため、uncaughtExceptionメソッドは呼び出されません。例外はそこでキャプチャされています。
Emil Sit
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.