Spring命令行应用程序在Async方法调用完成后挂起

时间:2017-10-17 14:40:47

标签: java spring multithreading asynchronous spring-boot

我有一个Spring引导应用程序,它使用CommandLineRunner和Spring @Async注释来异步运行方法。一切正常,但是当我的所有线程都完成后,应用程序就会挂起而不是退出。

以下是我在应用程序中的最小示例:

Application.java

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

ApplicationStartup.java

@Component
public class ApplicationStartup implements CommandLineRunner {

    private final AsyncService asyncService;

    @Inject
    public ApplicationStartup(final AsyncService asyncService) {
        this.asyncService = asyncService;
    }

    @Override
    public void run(final String... strings) throws Exception {
        //my logic is more complicated than this, but this illustrates my point
        for (int i = 0; i < 1000; i++) {
            asyncService.runAsyncMethod();
        }
    }
}

AsyncService.java

@Service
@Transactional
public class AsyncService {

    @Async
    public void runAsyncMethod() {
        //perform call to an API and process results
    }

}

ExecutorConfig.java

@Configuration
public class ExecutorConfig() {
    @Bean
    public ThreadPoolTaskExecutor asyncExecutor() {
        final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(64);
        executor.setMaxPoolSize(64);
        executor.setQueueCapacity(500);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setThreadNamePrefix("Scrub-");
        executor.setKeepAliveSeconds(60);
        executor.initialize();
        return executor;
    }
}

我的所有线程都调用runAsyncMethod()并且每个方法调用都成功完成,但是应用程序只是挂起。

我尝试改变一些执行器设置。起初我没有keepAliveSeconds,所以我想添加它会修复它,但它仍然在所有线程完成后挂起。我将corePoolSize更改为0,这使应用程序在完成时退出,但它一直只使用1个线程。

关于为什么应用程序没有退出上述配置的任何想法?

3 个答案:

答案 0 :(得分:1)

您错过了加入异步作业,这就是run方法在所有线程完成之前退出(远)的原因 - 并且尴尬的行为是&#34;更多可理解&#34;

根据doc,您可以加入:

...
CompletableFuture<Void>[] myJobs = new CompletableFuture<>[N];
...
for (int i = 0; i < N; i++) {
        myJobs[i] = asyncService.runAsyncMethod();
}
...
CompletableFuture.allOf(myJobs).join();

您的runAsyncMethod()需要返回CompletableFuture<Void>。为此,您可以return CompletableFuture.completedFuture(null);

答案 1 :(得分:0)

即使marked as correct答案是有效的。这不是完整的答案。

在没有@EnableAsync且没有WEB环境.web(WebApplicationType.NONE)的情况下,spring boot应用程序一旦启动就自动停止(因为无事可做/等待)。因此,即使您不在应用中执行apringApp.close(),而仅在app.run(commandLine)中执行,.close()方法也会自动调用。

但是一旦您添加了@EnableAsync-行为就会发生变化,因为可能会有异步工作,因此应用程序一旦启动就不会停止。而且,如果没有停止代码,则该应用程序仍然可以运行(挂起)。

要解决此问题,您需要做两件事:

示例:

    @EnableAutoConfiguration
    @EnableAsync
    public static class SpringApp extends SpringApplication {

        @Bean
        public TaskExecutor taskExecutor () {
            return new SimpleAsyncTaskExecutor();
        }

        @Autowired
        private Service service;

        @EventListener
        public void handleContextRefresh(ContextRefreshedEvent event){
            CompletableFuture<Void> aggregateFuture = service.doWork();
            // avoid exiting this method before all job complected prevents app from hanging
            aggregateFuture.join();

        }
    }
    public static void main(String[] args) {
        SpringApplicationBuilder app = new SpringApplicationBuilder(SpringApp.class).web(WebApplicationType.NONE);
        app.run()
           .close();  // <--- THIS!
    }

答案 2 :(得分:0)

是的,我遇到了与CommandLine应用类似的观察。在完成所有异步任务后,该应用程序不会关闭。按照@msangel的建议,我必须使用app.run()。close()关闭该应用程序。