英文:
Controller waiting even after using async and completable future
问题
I've translated the text you provided into Chinese as requested:
我正在开发一个Spring Boot应用程序,在这个应用程序中,我想要使用CompletableFuture和@Async注解使我的控制器异步化。为了测试实现,我故意在服务方法中添加了Thread.sleep()调用。然而,即使使用了这些异步方法,控制器仍然在执行之前等待一段时间。我已经验证了我的控制器和服务在不同的线程上运行,通过它们不同的线程ID来证明。我不确定为什么会发生这种情况,我会感激任何关于如何解决这个问题的见解或建议。
以下是代码示例:
//控制器
@PostMapping
public CompletableFuture<User> createUser(@RequestBody User user) {
System.out.println(Thread.currentThread().getId() + "-----------------------------------" + System.currentTimeMillis() + "----------" + "inside controller");
return userService.createUser(user);
}
// 服务
@Override
@Async("asyncExecutor")
public CompletableFuture<User> createUser(User user) {
System.out.println(Thread.currentThread().getId() + "-----------------------------------" + System.currentTimeMillis() + "-------------inside services");
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000000000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getId());
return userRepository.save(user);
});
}
为了更清楚地理解问题,提供一些代码示例或具体细节将会很有帮助。
//输出
21-----------------------------------1683404458540----------inside controller
34-----------------------------------1683404458543-------------inside services
2023-05-07 01:51:28.812 WARN 36119 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.context.request.async.AsyncRequestTimeoutException]
请告诉我如果您需要进一步的帮助或解释。
英文:
I am developing a Spring Boot application where I want to make my controller asynchronous using CompletableFuture and the @Async annotation. To test the implementation, I intentionally added a Thread.sleep() call in the service method. However, even with these asynchronous approaches, the controller is still waiting for some time before executing. I have verified that my controller and service are running on different threads, as evidenced by their distinct thread IDs. I am unsure why this is happening, and I would appreciate any insights or suggestions on how to resolve this issue.
code is given below:
//controller
@PostMapping
public CompletableFuture<User> createUser(@RequestBody User user) {
System.out.println(Thread.currentThread().getId()+"-----------------------------------"+System.currentTimeMillis()+"----------"+"inside controller");
return userService.createUser(user);
}
// service
@Override
@Async("asyncExecutor")
public CompletableFuture<User> createUser(User user) {
System.out.println(Thread.currentThread().getId()+"-----------------------------------"+System.currentTimeMillis()+"-------------inside services");
return CompletableFuture.supplyAsync(() ->{
try {
Thread.sleep(1000000000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getId());
return userRepository.save(user);
});
}
It would be helpful to provide some code examples or specific details about your implementation in order to give a clearer understanding of the problem.
//output
21-----------------------------------1683404458540----------inside controller
34-----------------------------------1683404458543-------------inside services
2023-05-07 01:51:28.812 WARN 36119 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.context.request.async.AsyncRequestTimeoutException]
答案1
得分: 1
Thread.sleep(1000000000);
似乎对于(构建)测试来说太长了... 这相当于约 12(太阳)天!
这个部分是代码,不需要翻译。
-
"App":
package com.example.demo; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import lombok.extern.slf4j.Slf4j; @EnableAsync @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Slf4j @Service static class MyAsyService { @Value("${simulation.sleep.seconds:10}") private Long simSeconds; @Async CompletableFuture<String> doSomething(String req) throws InterruptedException { log.info("in service..."); TimeUnit.SECONDS.sleep(simSeconds); log.info("...leaving service"); return CompletableFuture.completedFuture(req + "_" + UUID.randomUUID()); } } @Slf4j @Controller static class MyController { @Autowired MyAsyService service; @GetMapping("/hello") @ResponseBody CompletableFuture<String> hello(@RequestParam(required = false, defaultValue = "anonymous") String req) throws InterruptedException { log.info("in controller..."); CompletableFuture<String> result = service.doSomething(req); log.info("...controller done!"); return result; } } }
-
在服务器日志中:
... o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms ... DemoApplication$MyController : in controller.... <= req#1 ... DemoApplication$MyController : ....controller done! ... DemoApplication$MyAsyService : in service.... ... DemoApplication$MyAsyService : ....leaving service ... DemoApplication$MyController : in controller.... <= req#2 ... DemoApplication$MyController : ....controller done! ... DemoApplication$MyAsyService : in service.... ... DemoApplication$MyAsyService : ....leaving service ... DemoApplication$MyController : in controller.... <= req#3 ... DemoApplication$MyController : ....controller done! ... DemoApplication$MyAsyService : in service.... ... DemoApplication$MyAsyService : ....leaving service
-
(自动化、模拟 MVC) 测试(最难的部分;):
package com.example.demo; import static org.hamcrest.core.StringStartsWith.startsWith; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.UUID; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.RequestBuilder; @WebMvcTest class DemoApplicationTests { @Autowired MockMvc mockMvc; @Value("${simulation.sleep.seconds}") // -> src/test/resources/application.properties Long simSeconds; @Test void testController() throws Exception { String uid = UUID.randomUUID().toString(); MvcResult mvcResult = mockMvc.perform(get("/hello?req={v}", uid)) //1. .andExpect(request().asyncStarted()) // 2. .andReturn(); // 3. mockMvc.perform(asyncDispatch(mvcResult/*, (simSeconds + 1) * 1000*/)) // 4!* .andExpectAll( // ... status().isOk(), content().string(startsWith(uid))); } // 4*: 当我们花费/模拟超过约10秒时,我们必须并且可以重新实现/丰富: static RequestBuilder asyncDispatch(MvcResult mvcResult, long timeToWait) { // 从 MockMvcRequestBuilders#asyncDispatch 复制/采用 mvcResult.getAsyncResult(timeToWait); return servletContext -> { MockHttpServletRequest request = mvcResult.getRequest(); request.setDispatcherType(DispatcherType.ASYNC); request.setAsyncStarted(false); return request; }; } }
感谢
- https://sadique.io/blog/2015/11/24/testing-async-responses-using-mockmvc/
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.html#asyncDispatch(org.springframework.test.web.servlet.MvcResult)
将服务更改为:
CompletableFuture.supplyAsync(() ->{
try { //...
..没有显著影响。
英文:
Thread.sleep(1000000000);
seems too much for a (build) test ... That refers to ~ 12 (solar) days!
This works
-
"App":
package com.example.demo; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import lombok.extern.slf4j.Slf4j; @EnableAsync @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Slf4j @Service static class MyAsyService { @Value("${simulation.sleep.seconds:10}") private Long simSeconds; @Async CompletableFuture<String> doSomething(String req) throws InterruptedException { log.info("in service..."); TimeUnit.SECONDS.sleep(simSeconds); log.info("...leaving service"); return CompletableFuture.completedFuture(req + "_" + UUID.randomUUID()); } } @Slf4j @Controller static class MyController { @Autowired MyAsyService service; @GetMapping("/hello") @ResponseBody CompletableFuture<String> hello(@RequestParam(required = false, defaultValue = "anonymous") String req) throws InterruptedException { log.info("in controller..."); CompletableFuture<String> result = service.doSomething(req); log.info("...controller done!"); return result; } } }
-
in server logs:
... o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms ... DemoApplication$MyController : in controller.... <= req#1 ... DemoApplication$MyController : ....controller done! ... DemoApplication$MyAsyService : in service.... ... DemoApplication$MyAsyService : ....leaving service ... DemoApplication$MyController : in controller.... <= req#2 ... DemoApplication$MyController : ....controller done! ... DemoApplication$MyAsyService : in service.... ... DemoApplication$MyAsyService : ....leaving service ... DemoApplication$MyController : in controller.... <= req#3 ... DemoApplication$MyController : ....controller done! ... DemoApplication$MyAsyService : in service.... ... DemoApplication$MyAsyService : ....leaving service
-
(automatic, mock mvc) test (hardest part;):
package com.example.demo; import static org.hamcrest.core.StringStartsWith.startsWith; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.UUID; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.RequestBuilder; @WebMvcTest class DemoApplicationTests { @Autowired MockMvc mockMvc; @Value("${simulation.sleep.seconds}") // -> src/test/resources/application.properties Long simSeconds; @Test void testController() throws Exception { String uid = UUID.randomUUID().toString(); MvcResult mvcResult = mockMvc.perform(get("/hello?req={v}", uid)) //1. .andExpect(request().asyncStarted()) // 2. .andReturn(); // 3. mockMvc.perform(asyncDispatch(mvcResult/*, (simSeconds + 1) * 1000*/)) // 4!* .andExpectAll( // ... status().isOk(), content().string(startsWith(uid))); } // 4*: when we take/simulate more than ~10 seconds, we have to & can re-implement/enrich: static RequestBuilder asyncDispatch(MvcResult mvcResult, long timeToWait) { // copied/adopted from MockMvcRequestBuilders#asyncDispatch mvcResult.getAsyncResult(timeToWait); return servletContext -> { MockHttpServletRequest request = mvcResult.getRequest(); request.setDispatcherType(DispatcherType.ASYNC); request.setAsyncStarted(false); return request; }; } }
Thx To
- https://sadique.io/blog/2015/11/24/testing-async-responses-using-mockmvc/
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.html#asyncDispatch(org.springframework.test.web.servlet.MvcResult)
Changing service to:
CompletableFuture.supplyAsync(() ->{
try { //...
..has no significant effect.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论