我正在构建一个需要在Android上进行一些蓝牙操作的库。我想返回一个 Future 实例,所以任何使用我的库的人都可以在未来返回的实例上调用 .get(),并且可以自己处理 ExecutionException、TimeoutException 和 InterruptedException。但是,我想自己检测超时,因为我需要一些清理逻辑,例如断开与设备的连接等。我怎样才能做到这一点?
您可以在Future
周围实现一个包装类,该包装类委托给另一个包装类(当前您获得Future
的任何地方返回的包装类)。 像这样:
final class DelegatingFuture<T> implements Future<T> {
private final Future<T> delegate;
DelegatingFuture(final Future<T> delegate) {
this.delegate = Objects.requireNonNull(delegate);
}
// All other methods simply delegate to 'delegate'
@Override
public T get()
throws InterruptedException, ExecutionException {
try {
return this.delegate.get();
} catch (final Exception ex) {
// Handle cleanup...
throw ex;
}
}
// Something similar for get(long timeout, TimeUnit unit)
}
然后简单地return new DelegatingFuture<>(currentFuture);
你在哪里分发这些。
超时与具有超时的get
方法的调用方相关,并且仅与该调用方相关。超时并不意味着取消。例如,以下代码是Future
API 的合法用法:
ExecutorService es = Executors.newSingleThreadExecutor();
Future<String> f = es.submit(() -> {
Thread.sleep(3000);
return "hello";
});
for(;;) try {
String s = f.get(500, TimeUnit.MILLISECONDS);
System.out.println("got "+s);
break;
}
catch(TimeoutException ex) {
// perhaps, do some other work
System.out.println("will wait something more");
}
catch (ExecutionException ex) {
System.out.println("failed with "+ex);
break;
}
es.shutdown();
将清理与实际用于查询结果的方法绑定并不是一种有用的方法。该方法的调用方提供的超时与实际操作无关。甚至不能保证在操作结束之前会查询结果或根本不会查询结果。
清理应在操作完成或将来显式取消时进行。如果调用方打算在超时后取消,则调用方只需在捕获TimeoutException
后调用cancel
。
一种经常指出的方法是使用CompletionService
,例如
static final ExecutorService MY__EXECUTOR = Executors.newCachedThreadPool();
static final CompletionService<String> COMPLETION_SERVICE
= new ExecutorCompletionService<>(MY__EXECUTOR);
static final Future<?> CLEANER = MY__EXECUTOR.submit(() -> {
for(;;) try {
Future<String> completed = COMPLETION_SERVICE.take();
System.out.println("cleanup "+completed);
} catch(InterruptedException ex) {
if(MY__EXECUTOR.isShutdown()) break;
}
});
public static Future<String> doSomeWork() {
return COMPLETION_SERVICE.submit(() -> {
Thread.sleep(3000);
return "hello";
});
}
您可以控制何时轮询已完成的期货,例如在另一个后台线程中,如示例中所示,或在开始新作业之前。
你可以像测试它一样
Future<String> f = doSomeWork();
try {
String s = f.get(500, TimeUnit.MILLISECONDS);
System.out.println("got "+s);
}
catch(TimeoutException ex) {
System.out.println("no result after 500ms");
}
catch (ExecutionException ex) {
System.out.println("failed with "+ex);
}
if(f.cancel(true)) System.out.println("canceled");
f = doSomeWork();
// never calling get() at all
但老实说,我一直不明白为什么这么复杂的事情实际上是必要的。如果您想在正确的时间进行清理,您可以使用
static final ExecutorService MY__EXECUTOR = Executors.newCachedThreadPool();
public static Future<String> doSomeWork() {
Callable<String> actualJob = () -> {
Thread.sleep(3000);
return "hello";
};
FutureTask<String> ft = new FutureTask<>(actualJob) {
@Override
protected void done() {
System.out.println("cleanup "+this);
}
};
MY__EXECUTOR.execute(ft);
return ft;
}
以达到相同的目的。
甚至更简单
static final ExecutorService MY__EXECUTOR = Executors.newCachedThreadPool();
public static Future<String> doSomeWork() {
Callable<String> actualJob = () -> {
Thread.sleep(3000);
return "hello";
};
return MY__EXECUTOR.submit(() -> {
try {
return actualJob.call();
}
finally {
// perform cleanup
System.out.println("cleanup");
}
});
}
在任一情况下,无论作业是成功完成、失败还是被取消,都将执行清理。如果使用了cancel(true)
并且实际作业支持中断,则清理也将立即执行。