请考虑以下代码:
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
之外的这个例外。
答案 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