web-dev-qa-db-ja.com

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

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

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

例えば:

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が例外をスローしても。ここで何が起こっているのか、手がかりはありますか?

ありがとう!

187
Tom

docs から:

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

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);
        }
    }
}
141
nos

WARNING:このソリューションは呼び出しスレッドをブロックすることに注意する必要があります。


タスクによってスローされた例外を処理する場合は、通常、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をサブクラス化するよりもはるかに望ましい方法です。また、例外が回復可能なものである場合、タスクを再送信する機会も与えられます。

231
skaffman

この動作の説明は afterExecuteのjavadoc にあります。

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

16
Drew Wills

Executorに送信された提供されたrunnableをラップすることで回避しました。

CompletableFuture.runAsync(

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

        executorService
);
9
momomo

VerboseRunnablejcabi-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
);
6
yegor256

別の解決策は、ManagedTaskおよびManagedTaskListenerを使用することです。

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

メソッド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());
    }
}

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

3
CSchulz

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

1
Cristian Botiza

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
Bass

これは動作します

  • SingleThreadExecutorから派生したものですが、簡単に調整できます
  • Java 8 lamdasコード、ただし修正は簡単

多くのタスクを取得できる単一のスレッドを持つExecutorを作成します。そして、現在のものが次を開始するために実行を終了するのを待ちます

不注意なエラーまたは例外の場合、ncaughtExceptionHandlerがキャッチします

 public final class SingleThreadExecutorWithExceptions {[..____。] 
 public static ExecutorService newSingleThreadExecutorWithExceptions(final Thread.UncaughtExceptionHandler uncaughtExceptionHandler){
 
 ThreadFactory factory =(実行可能ランナブル)- > {
 final Thread newThread = new Thread(runnable、 "SingleThreadExecutorWithExceptions"); 
 newThread.setUncaughtExceptionHandler((final Thread caugthThread、final Throwable throwable)-> {
 uncaughtExceptionHandler.uncaughtException (caugthThread、throwable); 
}); 
 return newThread; 
}; 
 return new FinalizableDelegatedExecutorService 
(new ThreadPoolExecutor(1、1、 
 0L、TimeUnit.MILLISECONDS、
 new LinkedBlockingQueue()、
 factory){
 
 
 prot ected void afterExecute(Runnable runnable、Throwable throwable){
 super.afterExecute(runnable、throwable); 
 if(throwable == null && runnable instanceof Future){
 try {
 Future future =(Future)runnable; 
 if(future.isDone()){
 future.get(); 
} 
} catch (CancellationException ce){
 throwable = ce; 
} catch(ExecutionException ee){
 throwable = ee.getCause(); 
} catch(InterruptedException ie) {
 Thread.currentThread()。interrupt(); //無視/リセット
} 
} 
 if(throwable!= null){
 uncaughtExceptionHandler.uncaughtException(Thread.currentThread()、throwable); 
} 
} 
}); 
} 
 
 
 
 private static class FinalizableDelegatedExecutorService 
 DelegatedExecutorServiceを拡張します{
 FinalizableDelegatedExecutorService(ExecutorService executor){
 super(executor); 
} 
 protected void finalize(){
 super .shutdown(); 
} 
} 
 
 /**
 * ExecutorServiceメソッドのみを公開するラッパークラス
 * ExecutorService実装の。
 */
 private static class DelegatedExecutorServiceはAbstractExecutorServiceを拡張します{
 private final 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(long timeout、TimeUnit unit)
 throws InterruptedException {
 return e.awaitTermination(timeout、unit); 
} 
 public Future submit(Runnable task){
 return e.submit(task); 
} 
 public Future submit(Callable task){
 return e.submit( task); 
} 
 public Future submit(Runnable task、T result){
 return e.submit(task、result); 
} 
 public List> invokeAll(Collection> tasks)
 throws InterruptedException {
 return e.invokeAll(tasks); 
} 
 public List> invokeAll(Collection> tasks 、
 long timeout、TimeUnit unit)
 throws InterruptedException {
 return e.invokeAll(tasks、timeout、unit); 
 } 
 public T invokeAny(Collection> tasks)
 throws InterruptedException、ExecutionException {
 return e.invokeAny(tasks); 
} 
 public T invokeAny(Collection> tasks、
 long timeout、TimeUnit unit)
 throws InterruptedException、ExecutionException、TimeoutException {
 return e.invokeAny(tasks、timeout、unit); 
} 
} 
 
 
 
 private SingleThreadExecutorWithExceptions(){} 
} 
0
obesga_tirant

これは、次のようにAbstractExecutorService :: submitrunnableRunnableFutureFutureTask以外)にラップしているためです。

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はそれをWorkerに渡し、Worker.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