简体   繁体   English

从 Java ExecutorService 捕获线程异常

[英]Catching thread exceptions from Java ExecutorService

I'm working on a software development framework for parallel computing JavaSeis.org .我正在开发用于并行计算的软件开发框架JavaSeis.org I need a robust mechanism for reporting thread exceptions.我需要一个强大的机制来报告线程异常。 During development, knowing where exceptions came from has high value, so I would like to err on the side of over-reporting.在开发过程中,知道异常来自哪里具有很高的价值,所以我想在过度报告方面犯错。 I would also like to be able to handle Junit4 testing in threads as well.我还希望能够在线程中处理 Junit4 测试。 Is the approach below reasonable or is there a better way ?下面的方法是合理的还是有更好的方法?

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestThreadFailure {

  public static void main(String[] args) {
    int size = 1;
    ExecutorService exec = Executors.newFixedThreadPool(size);
    ThreadFailTask worker = new ThreadFailTask();
    Future<Integer> result = exec.submit(worker);
    try {
      Integer value = result.get();
      System.out.println("Result: " + value);
    } catch (Throwable t) {
      System.out.println("Caught failure: " + t.toString());
      exec.shutdownNow();
      System.out.println("Stack Trace:");
      t.printStackTrace();
      return;
    }
    throw new RuntimeException("Did not catch failure !!");
  }

  public static class ThreadFailTask implements Callable<Integer> {
    @Override
    public Integer call() {
      int nbuf = 65536;
      double[][] buf = new double[nbuf][nbuf];
      return new Integer((int) buf[0][0]);
    }
  }
}

Consider calling execute() instead of submit() on the ExecutorService .考虑在ExecutorService上调用execute()而不是submit() A Thread invoked with execute() will invoke the Thread.UncaughtExceptionHandler when it fails.使用execute()调用的Thread将在Thread.UncaughtExceptionHandler时调用Thread.UncaughtExceptionHandler

Simply make a ThreadFactory that installs a Thread.UncaughtExceptionHandler on all Threads and then invoke your work with execute() on the ExecutorService instead of submit() .只需创建一个在所有Threads上安装Thread.UncaughtExceptionHandlerThreadFactory ,然后在ExecutorService上使用execute()而不是submit()调用您的工作。

Have a look at this related stack overflow question .看看这个相关的堆栈溢出问题

I don't believe there is a standard 'hook' to get to these exceptions when using submit() .我不相信在使用submit()时有一个标准的“钩子”可以解决这些异常。 However, if you need to support submit() (which sounds reasonable, given that you use a Callable ), you can always wrap the Callables and Runnables :但是,如果您需要支持submit() (这听起来很合理,因为您使用的是Callable ),您始终可以包装 Callables 和 Runnables :

ExecutorService executor = new ThreadPoolExecutor(1, 10, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>()) {
    @Override
    public <T> Future<T> submit(final Callable<T> task) {
        Callable<T> wrappedTask = new Callable<T>() {
            @Override
            public T call() throws Exception {
                try {
                    return task.call();
                }
                catch (Exception e) {
                    System.out.println("Oh boy, something broke!");
                    e.printStackTrace();
                    throw e;
                }
            }
        };

        return super.submit(wrappedTask);
    }
};

Of course, this method only works if you're the one building the ExecutorService in the first place.当然,此方法仅在您首先构建ExecutorService时才有效。 Furthermore, remember to override all three submit() variants.此外,请记住覆盖所有三个submit()变体。

As explained in this thread What is the difference between submit and execute method with ThreadPoolExecutor , using execute will only work if you implement Runnable and not Callable as execute cannot return a Future.正如该线程中所解释的,使用ThreadPoolExecutor 提交和执行方法有什么区别,只有在实现 Runnable 而不是 Callable 时使用 execute 才有效,因为 execute 不能返回 Future。

I think in your scenario you should build the future object so that it can accommodate the exception stuff also.我认为在您的场景中,您应该构建未来对象,以便它也可以容纳异常内容。 So in case of exception you build the error message object.因此,如果出现异常,您将构建错误消息对象。

My original question asked how to implement "robust" thread exception handling with Java ExecutorService.我最初的问题是问如何使用 Java ExecutorService 实现“强大的”线程异常处理。 Thanks to Angelo and Greg for pointers on how exception handling works with ExecutorService.submit() and Future.get().感谢 Angelo 和 Greg 提供有关异常处理如何与 ExecutorService.submit() 和 Future.get() 一起工作的指针。 My revised code fragment is shown below.我修改后的代码片段如下所示。 The key point I learned here is that Future.get() catches all exceptions.我在这里学到的关键点是 Future.get() 捕获所有异常。 If the the thread was interrupted or cancelled, you get the appropriate exception, otherwise, the exception is wrapped and re-thrown as an ExecutionException.如果线程被中断或取消,您会得到适当的异常,否则,异常被包装并作为 ExecutionException 重新抛出。

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestThreadFailure {

  public static void main(String[] args) {
    int size = 1;
    ExecutorService exec = Executors.newFixedThreadPool(size);
    ThreadFailTask worker = new ThreadFailTask();
    Future result = exec.submit(worker);
    try {
      Integer value = result.get();
      System.out.println("Result: " + value);
    } catch (ExecutionException ex) {
      System.out.println("Caught failure: " + ex.toString());
      exec.shutdownNow();
      return;
    } catch (InterruptedException iex) {
      System.out.println("Thread interrupted: " + iex.toString());
    } catch (CancellationException cex) {
      System.out.println("Thread cancelled: " + cex.toString());
    }
    exec.shutdownNow();
    throw new RuntimeException("Did not catch failure !!");
  }

  public static class ThreadFailTask implements Callable {
    @Override
    public Integer call() {
      int nbuf = 65536;
      double[][] buf = new double[nbuf][nbuf];
      return new Integer((int) buf[0][0]);
    }
  }
}

I didn't have a great deal of luck with other answers because I needed the actual exception instance, itself, not just a printed stack trace.我对其他答案的运气并不好,因为我需要实际的异常实例本身,而不仅仅是打印的堆栈跟踪。 For me, the accepted answer involving ThreadPoolExecutor#afterExecute() of the question " Why is UncaughtExceptionHandler not called by ExecutorService? " worked.对我来说,关于问题“ 为什么 UncaughtExceptionHandler 没有被 ExecutorService 调用? ”的ThreadPoolExecutor#afterExecute()的公认答案有效。

See the following sample code:请参阅以下示例代码:

List<Runnable> tasks = new LinkedList<>();
for (int i = 0; i < numThreads; ++i) {
    Runnable task = new Runnable() {
        @Override
        public void run() {
            throw new RuntimeException();
        }
    };

    tasks.add(task);
}

Optional<Throwable> opEmpty = Optional.empty();
/*
 * Use AtomicReference as a means of capturing the first thrown exception, since a
 * spawned thread can't "throw" an exception to the parent thread.
 */
final AtomicReference<Optional<Throwable>> firstThrownException =
        new AtomicReference<>(opEmpty);

/*
 * Use new ThreadPoolExecutor instead of Executors.newFixedThreadPool() so
 * that I can override afterExecute() for the purposes of throwing an
 * exception from the test thread if a child thread fails.
 */
ExecutorService execSvc = new ThreadPoolExecutor(numThreads, numThreads,
            0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()) {
    @Override
    public void afterExecute(Runnable task, Throwable failureCause) {
        if(failureCause == null) {
            // The Runnable completed successfully.
            return;
        }
        // only sets the first exception because it will only be empty on the first call.
        firstThrownException.compareAndSet(Optional.<Throwable>empty(), Optional.of(failureCause));
    }
};

for (Runnable task : tasks) {
    execSvc.execute(task);
}
execSvc.shutdown();
execSvc.awaitTermination(1, TimeUnit.HOURS);

assertEquals(firstThrownException.get(), Optional.empty());

To Handling exceptions in ExecutorService you have to take the advantage of Callable and Future .要在ExecutorService 中处理异常,您必须利用CallableFuture

Callable is similar to Runnable and both are functional interface but run() of Runnable doesn't throws exception and the return type is void where as call() of Callable returns a generics and throws exception. CallableRunnable类似,两者都是函数式接口,但 Runnable 的run()不会抛出异常,并且返回类型为 void,而 Callable 的call()返回泛型并抛出异常。

Java-8 way: Java-8 方式:

ExecuterService executor = null; 
Future<Integer> future = null;

Callable<Integer> yourTask = () -> {
    //your implementation here();
    //your implementation here();
};

try 
   {
        executor = Executors.newCachedThreadPool();
        future = executor.submit(yourTask );
        Integer result = future.get();
        System.out.println(result);
   }
   catch (ExecutionException | TimeoutException | InterruptedException e) 
   {
    // TODO: handle exception
   }
   finally 
   {
       executer.shutdown();
   }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM