[英]What's the intended pattern of canceling already running CompletableFutures
I couldn't find any information in "Java 8 in Action" on why CompletableFuture
purposefully ignores mayInterruptIfRunning
.我在“Java 8 in Action”中找不到任何关于
CompletableFuture
故意忽略mayInterruptIfRunning
的信息。 But even if so, I don't really see any hook for custom cancel(boolean)
, which would come in handy in cases, where interruption doesn't affect a blocking operation (such as I/O streams, simple locks, and so on).但即使是这样,我也没有真正看到自定义
cancel(boolean)
的任何钩子,这在中断不影响阻塞操作(例如 I/O 流、简单锁等)的情况下会派上用场上)。 So far it seems that tasks themselves are the intended hooks and working on the abstraction level of Future
simply won't do any good here.到目前为止,任务本身似乎是预期的钩子,并且在
Future
的抽象级别上工作在这里根本不会有任何好处。
Therefore I'm asking about the minimum set of boilerplate code one has to introduce to squeeze some neet custom cancellation mechanism out of this situation.因此,我在询问必须引入的最小样板代码集,以从这种情况中挤出一些 neet 自定义取消机制。
A CompletableFuture
is an object encapsulating one of three state: CompletableFuture
是一个 object 封装了三个 state 之一:
The transition from “Not completed” to one of the other states can be triggered by a function passed to one of its factory methods or to one of the CompletionStage
implementation methods.从“未完成”到其他状态之一的转换可以由传递给其工厂方法之一或
CompletionStage
实现方法之一的 function 触发。
But it's also possible to invoke complete
or completeExceptionally
on it.但也可以在其上调用
complete
或completeExceptionally
。 In that regard, calling cancel
on it has the same effect as calling completeExceptionally(new CancellationException())
on it.在这方面,对其调用
cancel
与对其调用completeExceptionally(new CancellationException())
具有相同的效果。
That's also what its class name suggests, it's a future that can be completed rather than will [eventually] be completed.这也是它的 class 名称所暗示的,这是一个可以完成而不是[最终]完成的未来。 The class does not offer control over the existing completion attempts that may run in arbitrary threads and it doesn't treat the completion attempts scheduled by itself specially.
class 不提供对可能在任意线程中运行的现有完成尝试的控制,并且它不会专门处理自己安排的完成尝试。
It's also important to understand that when chaining operations via the CompletionStage
implementation methods, the resulting future only represents the last stage of the chain and cancelling it also can only affect the last stage.同样重要的是要了解,当通过
CompletionStage
实现方法链接操作时,生成的未来仅代表链的最后阶段,取消它也只会影响最后阶段。
Eg the following code例如下面的代码
CompletableFuture<?> cf = CompletableFuture.supplyAsync(() -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
String s = "value1";
System.out.println("return initial " + s);
return s;
}).thenApply(s -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
s = s.toUpperCase();
System.out.println("return transformed " + s);
return s;
}).thenAccept(s -> {
System.out.println("starting last stage");
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
System.out.println("processed " + s);
});
cf.cancel(false);
ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.DAYS);
will print将打印
return initial value1
return transformed VALUE1
demonstrating that only the last stage of the chain has been cancelled whereas the preceding stage ran to completion, entirely unaffected.证明只有链的最后一个阶段被取消,而前一个阶段运行到完成,完全不受影响。
Keeping a reference to the first stage in an attempt to cancel the entire chain would only work as long as the first stage has not completed, as an attempt to cancel an already completed future has no effect.保留对第一阶段的引用以尝试取消整个链只有在第一阶段尚未完成时才有效,因为尝试取消已经完成的未来无效。
long[] waitBeforeCancel = { 500, 1500 };
for(long l: waitBeforeCancel) {
CompletableFuture<String> first = CompletableFuture.supplyAsync(() -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
String s = "value1";
System.out.println("return initial " + s);
return s;
});
first.thenApply(s -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
s = s.toUpperCase();
System.out.println("return transformed " + s);
return s;
}).thenAccept(s -> {
System.out.println("starting last stage");
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
System.out.println("processed " + s);
});
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(l));
System.out.println("Trying to cancel");
first.cancel(false);
ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.DAYS);
System.out.println();
}
Trying to cancel
return initial value1
return initial value1
Trying to cancel
return transformed VALUE1
starting last stage
processed VALUE1
This demonstrates that the entire chain gets cancelled when the first stage is cancelled in time (except that the Supplier
's code of the first still completes due to the nonexistent interruption), whereas cancelling too late will not affect any stage.这说明当第一阶段及时取消时,整个链条被取消(除了第一阶段的
Supplier
代码由于不存在中断而仍然完成),而取消太晚不会影响任何阶段。
Remembering all CompletableFuture
instances, to be able to cancel all of them, would defeat the purpose of the API.记住所有
CompletableFuture
实例,以便能够取消所有这些实例,将违背 API 的目的。 You can use an executor that tracks all currently processed jobs, to forward cancellation and interruption to them when the last stage has been cancelled.您可以使用跟踪所有当前处理的作业的执行器,在最后一个阶段被取消时将取消和中断转发给它们。 The
CompletableFuture
implementation will forward the cancellation to dependent stages then. CompletableFuture
实现会将取消转发到相关阶段。 That way, completed stages still can be garbage collected.这样,完成的阶段仍然可以被垃圾收集。
The setup is a bit involved;设置有点复杂; the wrapper executor is needed before-hand to construct the
CompletableFuture
chain and the forwarding of the cancellation needs the last stage of the already constructed chain.预先需要包装器执行器来构建
CompletableFuture
链,并且取消的转发需要已经构建的链的最后阶段。 That's why I made a utility method accepting the chain construction code as a Function<Executor,CompletableFuture<T>>
:这就是为什么我制作了一个实用方法,将链构造代码作为
Function<Executor,CompletableFuture<T>>
接受:
static <T> Future<T> setupForInterruption(Function<Executor,CompletableFuture<T>> f) {
return setupForInterruption(f, ForkJoinPool.commonPool());
}
static <T> Future<T> setupForInterruption(
Function<Executor,CompletableFuture<T>> f, Executor e) {
AtomicBoolean dontAcceptMore = new AtomicBoolean();
Set<Future<?>> running = ConcurrentHashMap.newKeySet();
Executor wrapper = r -> {
if(dontAcceptMore.get()) throw new CancellationException();
FutureTask<?> ft = new FutureTask<>(r, null) {
@Override protected void done() { running.remove(this); }
};
running.add(ft);
e.execute(ft);
};
CompletableFuture<T> cf = f.apply(wrapper);
cf.whenComplete((v,t) -> {
if(cf.isCancelled()) {
dontAcceptMore.set(true);
running.removeIf(ft -> ft.cancel(true) || ft.isDone());
}
});
return cf;
}
This can be used like这可以像这样使用
long[] waitBeforeCancel = { 500, 1500, 2500, 3500 };
for(long l: waitBeforeCancel) {
Future<?> f = setupForInterruption(executor ->
CompletableFuture.supplyAsync(() -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
if(Thread.interrupted()) throw new IllegalStateException();
String s = "value1";
System.out.println("return initial " + s);
return s;
}, executor).thenApplyAsync(s -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
if(Thread.interrupted()) throw new IllegalStateException();
s = s.toUpperCase();
System.out.println("return transformed " + s);
return s;
}, executor).thenAcceptAsync(s -> {
System.out.println("starting last stage");
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
if(Thread.interrupted()) throw new IllegalStateException();
System.out.println("processed " + s);
}, executor));
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(l));
System.out.println("Trying to cancel");
f.cancel(true);
ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.DAYS);
System.out.println();
}
Trying to cancel
return initial value1
Trying to cancel
return initial value1
return transformed VALUE1
starting last stage
Trying to cancel
return initial value1
return transformed VALUE1
starting last stage
processed VALUE1
Trying to cancel
Since the API uses Supplier
, Function
, and Consumer
, none of them being allowed to throw InterruptedException
, this example code bears explicit test for interruption and throws IllegalStateException
instead.由于 API 使用
Supplier
、 Function
和Consumer
,它们中的任何一个都不允许抛出InterruptedException
,因此此示例代码需要对中断进行显式测试并抛出IllegalStateException
。 That's also the reason it uses parkNanos
which just immediately returns on interruption instead of Thread.sleep
in the first place.这也是它使用
parkNanos
的原因,它只是在中断时立即返回,而不是首先返回Thread.sleep
。 In real application scenarios, you'll likely call interruption sensitive methods and have to catch InterruptedException
, InterruptedIOException
, or InterruptedNamingException
(etc.) and convert them to an unchecked exception.在实际应用场景中,您可能会调用中断敏感方法,并且必须捕获
InterruptedException
、 InterruptedIOException
或InterruptedNamingException
(等)并将它们转换为未经检查的异常。
Note that above methods will always cancel with interruption, as the CompletableFuture
does not tell whether the cancellation was with interruption or not.请注意,上述方法总是会因中断而取消,因为
CompletableFuture
不会告诉取消是否因中断而发生。 If you want to get the value of this parameter, you need a front-end Future
implementation that reflects the result of the last stage, forwards cancellation to it, but passes the value of the mayInterruptIfRunning
to the currently running jobs.如果要获取此参数的值,则需要前端的
Future
实现,该实现反映上一阶段的结果,将取消转发给它,但将mayInterruptIfRunning
的值传递给当前正在运行的作业。
class FrontEnd<R> implements Future<R> {
final CompletableFuture<R> lastStage;
final Set<Future<?>> running;
FrontEnd(CompletableFuture<R> lastStage, Set<Future<?>> running) {
this.lastStage = lastStage;
this.running = running;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
boolean didCancel = lastStage.cancel(false);
if(didCancel)
running.removeIf(f -> f.cancel(mayInterruptIfRunning) || f.isDone());
return didCancel;
}
@Override
public boolean isCancelled() {
return lastStage.isCancelled();
}
@Override
public boolean isDone() {
return lastStage.isDone();
}
@Override
public R get() throws InterruptedException, ExecutionException {
return lastStage.get();
}
@Override
public R get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return lastStage.get(timeout, unit);
}
static <T> Future<T> setup(Function<Executor,CompletableFuture<T>> f) {
return setup(f, ForkJoinPool.commonPool());
}
static <T> Future<T> setup(Function<Executor,CompletableFuture<T>> f, Executor e) {
AtomicBoolean dontAcceptMore = new AtomicBoolean();
Set<Future<?>> running = ConcurrentHashMap.newKeySet();
Executor wrapper = r -> {
if(dontAcceptMore.get()) throw new CancellationException();
FutureTask<?> ft = new FutureTask<>(r, null) {
@Override protected void done() { running.remove(this); }
};
running.add(ft);
e.execute(ft);
};
CompletableFuture<T> cf = f.apply(wrapper);
cf.whenComplete((v,t) -> { if(cf.isCancelled()) dontAcceptMore.set(true); });
return new FrontEnd<>(cf, running);
}
}
You can imagine a CompletableFuture
to be just a (typed) handle to an async subprocess (of course it has some more benefits).您可以想象
CompletableFuture
只是异步子进程的(类型化)句柄(当然它还有更多好处)。 But it is NOT a controller of the async subprocess.但它不是异步子进程的 controller。 You not even have a direct access to it (or the thread executing it).
您甚至无法直接访问它(或执行它的线程)。
To implement an abort()
I did it this way, extending the CompletableFuture
:为了实现
abort()
我这样做了,扩展了CompletableFuture
:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
import x.y.MyEnum;
/**
* Provides an interruptible CompletableFuture implementation for one time usage.
*/
public final class InteruptibleCompletableFuture extends CompletableFuture<MyEnum>
{
/**
* the embedded executor service
*/
private ExecutorService myExecutorService;
/**
* Executes the given supplier asynchronously and returns the future to wait for.
*
* @param aSupplier the supplier to be executed
* @return the CompletableFuture to wait for
*/
public static InteruptibleCompletableFuture execute(Supplier<MyEnum> aSupplier)
{
InteruptibleCompletableFuture result = new InteruptibleCompletableFuture();
result.myExecutorService.submit( () ->
{
try
{
MyEnum res = aSupplier.get();
result.complete( res);
}
catch ( Throwable ex )
{
result.completeExceptionally( ex);
}
});
return result;
}
private InteruptibleCompletableFuture()
{
super();
myExecutorService = Executors.newSingleThreadExecutor();
}
@Override
public boolean complete( MyEnum value)
{
if ( isDone() )
{
// Future already completed
return false;
}
shutdownExecutorService();
return super.complete( value);
}
@Override
public boolean completeExceptionally( Throwable ex)
{
if ( isDone() )
{
// Future already completed
return false;
}
shutdownExecutorService();
return super.completeExceptionally( ex);
}
private void shutdownExecutorService()
{
try
{
myExecutorService.shutdownNow();
}
catch ( Throwable e )
{
// log error
}
}
}
The idea is that this implementation has a hand on the executor.这个想法是这个实现对执行者有帮助。 Forcing a termination from the main application delegates to the call
myExecutorService.shutdownNow()
.强制从主应用程序委托终止调用
myExecutorService.shutdownNow()
。 Read the javadoc of this method carefully.仔细阅读这个方法的javadoc。
Be aware that java has no legal method to kill a thread.请注意,java 没有合法的方法来杀死线程。 A call to myThread.interrupt() does not kill it.
对 myThread.interrupt() 的调用不会杀死它。 It just sets the interrupted flag and wakes up the thread if it was waiting in a monitor (throwing an InterruptedException).
如果线程在监视器中等待(抛出 InterruptedException),它只是设置中断标志并唤醒线程。 Your implementation must check the interrupted flag regularly if it is doing a batch job in a loop, and also react correctly to an InterruptedException.
如果您的实现在循环中执行批处理作业,则必须定期检查中断标志,并且还必须对 InterruptedException 做出正确反应。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.