【问题标题】:Spring Async is blocking and prevents nested async callsSpring Async 阻塞并防止嵌套异步调用
【发布时间】:2017-03-29 13:31:55
【问题描述】:

谁能告诉我有没有一种方法可以使用 Spring Framework 的 @Async 注释而不阻塞/等待结果?这是一些代码来澄清我的问题:

@Service
public class AsyncServiceA {

    @Autowired
    private AsyncServiceB asyncServiceB;

    @Async
    public CompletableFuture<String> a() {
        ThreadUtil.silentSleep(1000);
        return asyncServiceB.b();
    }
}


@Service
public class AsyncServiceB {

    @Async
    public CompletableFuture<String> b() {
        ThreadUtil.silentSleep(1000);
        return CompletableFuture.completedFuture("Yeah, I come from another thread.");
    }
}

和配置:

@SpringBootApplication
@EnableAsync
public class Application implements AsyncConfigurer {

    private static final Log LOG = LogFactory.getLog(Application.class);

    private static final int THREAD_POOL_SIZE = 1;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
        return args -> {
            final AsyncServiceA bean = ctx.getBean(AsyncServiceA.class);
            bean.a().whenComplete(LOG::info);
        };
    }

    @Override
    @Bean(destroyMethod = "shutdown")
    public ThreadPoolTaskExecutor getAsyncExecutor() {
        final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(THREAD_POOL_SIZE);
        executor.setMaxPoolSize(THREAD_POOL_SIZE);
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        // omitted
    }
}

当我运行应用程序时,执行程序通过调用AsyncServiceA.a() 并离开,但它仍然持有池中的线程等待CompletableFuture.get() 方法。由于池中只有一个线程,因此无法执行 AsyncServiceB.b()。我期望的是该线程在执行AsyncServiceA.a() 后返回到池中,然后可用于执行AsyncServiceB.b()

有没有办法做到这一点?

注意 1:我也尝试过 ListenableFuture,但结果是一样的。

注意 2:我已经成功手动完成了它(没有@Async),方法是像这样将执行器分配给每个方法:

异步服务A

public CompletableFuture<String> manualA(Executor executor) {
    return CompletableFuture.runAsync(() -> {
        LOG.info("manualA() working...");
        ThreadUtil.silentSleep(1000);
    }, executor)
            .thenCompose(x -> asyncServiceB.manualB(executor));
}

异步服务B

public CompletableFuture<String> manualB(Executor executor) {
    return CompletableFuture.runAsync(() -> {
        LOG.info("manualB() working...");
        ThreadUtil.silentSleep(1000);
    }, executor)
            .thenCompose(x -> CompletableFuture
                    .supplyAsync(() -> "Yeah, I come from another thread.", executor));
}

如果有人想知道,这里是ThreadUtil

public class ThreadUtil {

    public static void silentSleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

更新:添加了非阻塞异步注释https://jira.spring.io/browse/SPR-15401的问题

【问题讨论】:

  • 那么你对最大池大小为 1 的期望是什么...只有一个线程来处理异步方法,所以基本上没有什么是异步的。该线程被阻塞并且在完成之前不会返回到池中。
  • 我已经在上面的问题中解释了我的期望 - CompletableFuture 上的操作是非阻塞的,直到您调用 get。我不会在任何地方调用 get(),所以我希望线程可以使用这个 CompletableFuture 完成并返回到池中。不幸的是,@Async 注释强制调用 get() 方法,因此线程被阻塞 - 我想摆脱它。我知道这与大小为 1 的线程池不是异步的,但在非阻塞场景中它会像魅力一样工作。
  • 我不确定我是否理解您的最后评论。我从来没有达到 b() 方法调用 - a(),它是异步返回并阻塞线程 before b() 在另一个线程中被调用,并且池中没有线程用于 b( )。我确信 b() 永远不会被期望,因为我已经在输入方法和返回时添加了方面来记录。我也调试了几十次。
  • 我删除了评论(在我写另一个之前:))。 Spring 正在调用get(),因此它的工作方式与您编写内容的示例不同。所以基本上你是在比较苹果和橘子,你想要的不能这样实现,你仍然需要自己做曲。 spring 也用于supplyAsync 而不是runAsync。 (您可能想查看AsyncExecutionInterceptor 的来源,以了解 spring 的实际作用。
  • :) 是的,我看到了AsyncExecutionInterceptor 的工作原理,但我不喜欢它(因为我想控制何时调用get())。我希望对此有一些解决方法(使用配置或其他东西),因为这对我来说没有意义,为什么它会那样工作(阻止执行)。好吧,也许我要求太多了,应该做自定义解决方案,然后等待 Java 9 和 Spring 5.0 的正式发布,以及它的反应特性。

标签: java spring multithreading asynchronous nonblocking


【解决方案1】:

@Async 支持自 Spring 3.0 以来一直是 Spring 的一部分,这早于 Java8(或 7)的存在。尽管在以后的版本中添加了对CompletableFutures 的支持,但它仍可用于方法调用的简单异步执行。 (initial implementation 反映/显示呼叫)。

对于编写回调和非阻塞操作,异步支持从未被设计或打算这样做。

对于非阻塞支持,您可能希望等待 Spring 5 及其反应式/非阻塞核心,在此旁边,您可以随时 submit a ticket 以获得异步支持中的非阻塞支持。

【讨论】:

  • 谢谢!仅供参考,我添加了问题 - 更新描述中的链接。
【解决方案2】:

我已在工单上回复 https://jira.spring.io/browse/SPR-15401,但我也会在这里回复以限定 M. Deinum 的回复。

@Async 凭借其工作原理(通过 AOP 修饰方法调用)只能做一件事,就是将整个方法从同步转为异步。这意味着方法必须是同步的,而不是同步和异步的混合。

因此,ServiceA 进行一些睡眠,然后委托给异步 ServiceB 必须将睡眠部分包装在一些 @Async ServiceC 中,然后在 ServiceB 和 C 上编写。这样,ServiceA 变为异步并且不需要 @Async注释本身..

【讨论】:

  • 请原谅我的无知,但是这种方法不会仍然将调用 ServiceA 的线程与每个 @Async 服务调用上的底层 get() 调用绑定在一起吗?如果是这样,当从 Web 服务启动 ServiceA 时,这不会令人不悦吗?
猜你喜欢
  • 2013-12-25
  • 2016-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-24
  • 2017-09-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多