返回CompletableFuture而不暴露执行程序线程

返回CompletableFuture而不暴露执行程序线程

本文介绍了返回CompletableFuture而不暴露执行程序线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在库中公开一个方法,该方法返回CompletableFuture。该方法的计算发生在单线程执行器上,这是我的瓶颈,因此,我不希望在同一线程上进行任何后续工作。


如果我使用返回结果的简单方法 supplyAsync,我将把我的宝贵线程暴露给调用者,这些调用者可能正在添加同步操作(例如,通过thenAccept),这可能会占用该线程的CPU时间。



复制如下:

I expose a method in a library, which returns a CompletableFuture. That method's computation happens on a single-threaded Executor which is my bottleneck, hence I do not want any subsequent work to happen on the same thread.

If I use the simple approach of returning the result of "supplyAsync", I'll be exposing my precious thread to the callers, who may be adding synchronous operations (e.g. via thenAccept) which could take some CPU time on that thread.

Repro below:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CfPlayground {
    private ExecutorService preciousExecService = Executors.newFixedThreadPool(1);

    CfPlayground() {}

    private static void log(String msg) {
        System.out.println("[" + Thread.currentThread().getName() + "] " + msg);
    }

    CompletableFuture<String> asyncOp(String param) {
        return CompletableFuture.supplyAsync(() -> {
            log("In asyncOp");
            return "Hello " + param;
        }, preciousExecService);
    }

    void syncOp(String salutation) {
        log("In syncOp: " + salutation);
    }

    void run() {
        log("run");
        asyncOp("world").thenAccept(this::syncOp);
    }

    public static void main(String[] args) throws InterruptedException {
        CfPlayground compFuture = new CfPlayground();
        compFuture.run();
        Thread.sleep(500);
        compFuture.preciousExecService.shutdown();
    }
}

这确实显示:

[main] run
[pool-1-thread-1] In asyncOp
[pool-1-thread-1] In syncOp: Hello world

我发现的一个解决方案是引入另一个执行器,并在返回CompletableFuture之前与该执行程序添加一个no-op thenApplyAsync

One solution I found was to introduce another Executor, and add a no-op thenApplyAsync with that executor before returning the CompletableFuture

    CompletableFuture<String> asyncOp(String param) {
        return CompletableFuture.supplyAsync(() -> {
            log("In asyncOp");
            return "Hello " + param;
        }, preciousExecService).thenApplyAsync(s -> s, secondExecService);
    }

此方法有效,但感觉并不优雅-是否有更好的方法要做到这一点?

This works, but doesn't feel super elegant - is there a better way to do this?

推荐答案

没有任何功能可以使您的完成与从属操作的执行分开。当链接相关动作的线程已经完成注册并且您的执行者的线程完成了将来时,如果没有其他执行者被授予,哪个其他线程应该执行相关动作?

There is no feature to detach your completion from the execution of the dependent action. When the thread chaining the dependent action has already completed the registration and your executor’s thread completes the future, which other thread ought to execute the dependent action if no other executor was given?

您将方法与其他执行程序链接在一起的方法似乎是最好的方法。但是,请务必注意,如果完成异常,则在不评估传递给 thenApply 的函数的情况下传播异常。如果调用者链接了 whenComplete handle 之类的操作,则这种异常传播可能再次导致线程暴露或例外

Your approach of chaining another action with a different executor seems to be the best you can get. However, it’s important to note that in case of an exceptional completion, the exception gets propagated without evaluating functions passed to thenApply. This exception propagation could again lead to an exposure of the thread, if the caller chained an action like whenComplete, handle, or exceptionally.

另一方面,您无需指定辅助执行者,就可以使用没有执行程序参数的 async 方法,以获取默认的(公共Fork / Join)池。

On the other hand, you don’t need to specify a secondary executor, as you can use the async method without executor parameter, to get the default (common Fork/Join) pool.

因此链接 .whenCompleteAsync((x,y)-> {})是迄今为止解决问题的最佳方法。

So chaining .whenCompleteAsync((x,y) -> {}) is the best solution to your problem so far.

这篇关于返回CompletableFuture而不暴露执行程序线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-22 20:28