简体   繁体   English

取消已经运行的 CompletableFutures 的预期模式是什么

[英]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 之一:

  1. Not completed,没完成,
  2. Completed with a value, or以值完成,或
  3. Completed exceptionally异常完成

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.但也可以在其上调用completecompleteExceptionally 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 使用SupplierFunctionConsumer ,它们中的任何一个都不允许抛出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.在实际应用场景中,您可能会调用中断敏感方法,并且必须捕获InterruptedExceptionInterruptedIOExceptionInterruptedNamingException (等)并将它们转换为未经检查的异常。

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.

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