如何从提交给执行者的已取消+中断调用中获取异常?

时间:2015-05-07 15:54:57

标签: java multithreading concurrency executorservice

请考虑以下代码:

ExecutorService executor = Executors.newSingleThreadExecutor();
final CountDownLatch taskStarted = new CountDownLatch(1);
Future<String> future = executor.submit( new Callable<String>() {
    @Override
    public synchronized String call() throws Exception {
        try {
            taskStarted.countDown();
            this.wait( 60000 );
            return "foo";                   
        }
        catch( Exception iWantToGetThisExceptionOutside ) {
            iWantToGetThisExceptionOutside.printStackTrace();
            throw iWantToGetThisExceptionOutside;
        }
    }
});
assertTrue(taskStarted.await(60, TimeUnit.SECONDS));
try {
    future.get(500, TimeUnit.MILLISECONDS);
    fail("Timeout expected.");
} catch (ExecutionException | TimeoutException e) {
    e.printStackTrace();
}
future.cancel(true); //mayInterruptIfRunning
//how to get iWantToGetThisExceptionOutside here?

取消后有没有办法在主线程中获取iWantToGetThisExceptionOutside?我是否必须创建自己的执行者?

修改: 只是为了明确没有抛出ExecutionException,而是TimeoutException,它不包含任何原因。 iWantToGetThisExceptionOutside是正常的InterruptedException

EDIT2 : 一点澄清:任务相对简单。如果任务运行时间过长,我希望能够取消该任务。为此,我需要一个带有超时的get call,它会在超时时抛出异常。我仍然欢迎在我的日志中显示一个堆栈跟踪条目,其中显示 WHERE 任务已取消。为此,我需要在Callable之外的这个例外。

4 个答案:

答案 0 :(得分:2)

你抓到的ExecutionException在被抛出时应该绕着iWant... Exception

您可以通过检查主线程中Throwable语句中ExecutionException的{​​{1}}来添加自定义逻辑:

catch

注意

// pseudo-code if (e.getCause().[something, i.e. getMessage]) { // TODO something } 实施中,您call并重新catch使用相同的throw,这是没有意义的。

注意II

从可调用逻辑中 推断超时是没有意义的,尽管你总是可以编程方式计算从Exception开始到结束所需的时间call

超时的整点和call是调用者决定延迟任务花了太长时间。

为此,您可以在TimeoutException语句中抓取TimeoutException

如果你需要“装饰”你的catch,而是由于触发执行时间过长的特定原因,你可以:

  • 以编程方式计算从TimeoutException调用开始到call调用结束的时间,以及call将由{{1}包裹的自定义throw (非常难看),
  • Exception方法中对每个“子任务”进行自己的延迟执行,并ExecutionException自定义call进行任何超时

答案 1 :(得分:2)

您可以使用自定义的FutureTask

来帮助自己
public class TracingFutureTask<T> extends FutureTask<T> {
    private Throwable trace;
    private boolean done;

    public TracingFutureTask(Callable<T> callable) {
        super(callable);
    }
    public TracingFutureTask(Runnable runnable, T result) {
        super(runnable, result);
    }

    @Override
    public void run() {
        try { super.run(); }
        finally { synchronized(this) { done=true; notifyAll(); }}
    }

    @Override
    protected void setException(Throwable t) {
        trace=t;
        super.setException(t);
    }
    public synchronized Throwable getException() throws InterruptedException {
        while(!done) wait();
        return trace;
    }

    public synchronized Throwable getException(long timeout)
                        throws InterruptedException, TimeoutException {

        for(long deadline = System.currentTimeMillis()+timeout, toWait=timeOut;
            !done; toWait = deadline-System.currentTimeMillis()) {

            if ( toWait <=0 ) throw new TimeoutException(
                              "Thread did not end in " + timeout + " milliseconds!" );
            wait(toWait);
        }
        return trace;
    }

    public static <V> TracingFutureTask<V> submit(Executor e, Callable<V> c) {
        TracingFutureTask<V> ft=new TracingFutureTask<>(c);
        e.execute(ft);
        return ft;
    }
    public static <V> TracingFutureTask<V> submit(Executor e, Runnable r, V v) {
        TracingFutureTask<V> ft=new TracingFutureTask<>(r, v);
        e.execute(ft);
        return ft;
    }
}

除了基类之外还跟踪异常,但与基类不同,它甚至在作业被取消时也会记住它。这就是为什么在run()方法和getException()之间存在额外的同步,因为在取消的情况下,作业可以在异常之前进入取消状态(这意味着“完成”)已被记录,因此我们必须引入我们自己的done状态并进行适当的同步。

可以像:

一样使用
ExecutorService executor = Executors.newSingleThreadExecutor();
TracingFutureTask<String> future=TracingFutureTask.submit(executor, new Callable<String>(){
    @Override
    public synchronized String call() throws Exception {
        this.wait( 60000 );
        return "foo";                   
    }
});
try {
    future.get(500, TimeUnit.MILLISECONDS);
    fail("Timeout expected.");
} catch (ExecutionException | TimeoutException e) {
    e.printStackTrace();
}
if(future.cancel(true)) {
    System.err.println("cancelled.");
    Throwable t = future.getException();
    if(t!=null) t.printStackTrace(System.err.append("cancellation caused "));
}

(源自您的示例代码)

java.util.concurrent.TimeoutException
    at java.util.concurrent.FutureTask.get(FutureTask.java:205)
    at so.TestCancel.main(TestCancel.java:69)
cancelled.
cancellation caused java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)
    at so.TestCancel$1.call(TestCancel.java:64)
    at so.TestCancel$1.call(TestCancel.java:61)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at so.TracingFutureTask.run(TestCancel.java:33)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

答案 2 :(得分:1)

Future.get()方法是获取call()中抛出的任何异常的唯一方法。因此,只需在future.get();之后添加另一个future.cancel(true)

答案 3 :(得分:1)

而不是依赖于throw / catch,你可以将Callable中的异常简单地作为一个对象(使用synchronized shared state)。看起来很丑,但很有效。

    ExecutorService executor = Executors.newSingleThreadExecutor();
    final CountDownLatch taskStarted = new CountDownLatch(1);
    final CountDownLatch taskCompleted = new CountDownLatch(1);  // <- to sync on task completion
    final Exception[] wasSomethingWrong = new Exception[1];      // <- not thread safe, but works here  
    Future<String> future = executor.submit( new Callable<String>() {
        @Override
        public synchronized String call() throws Exception {
            try {
                taskStarted.countDown();
                this.wait( 60000 );
            }
            catch( Exception iWantToGetThisExceptionOutside ) {
                wasSomethingWrong[0] = iWantToGetThisExceptionOutside; // <-
            } finally {
                taskCompleted.countDown();  // <- 
            }
            return "foo";
        }
    });
    assertTrue(taskStarted.await(60, TimeUnit.SECONDS));
    try {
        future.get(500, TimeUnit.MILLISECONDS);
        fail("Timeout expected.");
    } catch (ExecutionException | TimeoutException e) {
        e.printStackTrace();
    }
    future.cancel(true); //mayInterruptIfRunning
    taskCompleted.await(60, TimeUnit.SECONDS);  // <- sync

    assertNotNull(wasSomethingWrong[0]);
    System.out.println(Arrays.toString(wasSomethingWrong[0].getStackTrace()));
    assertEquals(InterruptedException.class, wasSomethingWrong[0].getClass());  // <- PROFIT