Controller 使用异步和 CompletableFuture 后仍然等待。

huangapple go评论94阅读模式
英文:

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来证明。我不确定为什么会发生这种情况,我会感激任何关于如何解决这个问题的见解或建议。

以下是代码示例:

  1. //控制器
  2. @PostMapping
  3. public CompletableFuture<User> createUser(@RequestBody User user) {
  4. System.out.println(Thread.currentThread().getId() + "-----------------------------------" + System.currentTimeMillis() + "----------" + "inside controller");
  5. return userService.createUser(user);
  6. }
  7. // 服务
  8. @Override
  9. @Async("asyncExecutor")
  10. public CompletableFuture<User> createUser(User user) {
  11. System.out.println(Thread.currentThread().getId() + "-----------------------------------" + System.currentTimeMillis() + "-------------inside services");
  12. return CompletableFuture.supplyAsync(() -> {
  13. try {
  14. Thread.sleep(1000000000);
  15. } catch (InterruptedException e) {
  16. throw new RuntimeException(e);
  17. }
  18. System.out.println(Thread.currentThread().getId());
  19. return userRepository.save(user);
  20. });
  21. }

为了更清楚地理解问题,提供一些代码示例或具体细节将会很有帮助。

//输出

  1. 21-----------------------------------1683404458540----------inside controller
  2. 34-----------------------------------1683404458543-------------inside services
  3. 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:

  1. //controller
  2. @PostMapping
  3. public CompletableFuture&lt;User&gt; createUser(@RequestBody User user) {
  4. System.out.println(Thread.currentThread().getId()+&quot;-----------------------------------&quot;+System.currentTimeMillis()+&quot;----------&quot;+&quot;inside controller&quot;);
  5. return userService.createUser(user);
  6. }

// service

  1. @Override
  2. @Async(&quot;asyncExecutor&quot;)
  3. public CompletableFuture&lt;User&gt; createUser(User user) {
  4. System.out.println(Thread.currentThread().getId()+&quot;-----------------------------------&quot;+System.currentTimeMillis()+&quot;-------------inside services&quot;);
  5. return CompletableFuture.supplyAsync(() -&gt;{
  6. try {
  7. Thread.sleep(1000000000);
  8. } catch (InterruptedException e) {
  9. throw new RuntimeException(e);
  10. }
  11. System.out.println(Thread.currentThread().getId());
  12. return userRepository.save(user);
  13. });
  14. }

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

  1. 21-----------------------------------1683404458540----------inside controller
  2. 34-----------------------------------1683404458543-------------inside services
  3. 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(太阳)天!

这个部分是代码,不需要翻译。

  • starter

  • "App":

    1. package com.example.demo;
    2. import java.util.UUID;
    3. import java.util.concurrent.CompletableFuture;
    4. import java.util.concurrent.TimeUnit;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.beans.factory.annotation.Value;
    7. import org.springframework.boot.SpringApplication;
    8. import org.springframework.boot.autoconfigure.SpringBootApplication;
    9. import org.springframework.scheduling.annotation.Async;
    10. import org.springframework.scheduling.annotation.EnableAsync;
    11. import org.springframework.stereotype.Controller;
    12. import org.springframework.stereotype.Service;
    13. import org.springframework.web.bind.annotation.GetMapping;
    14. import org.springframework.web.bind.annotation.RequestParam;
    15. import org.springframework.web.bind.annotation.ResponseBody;
    16. import lombok.extern.slf4j.Slf4j;
    17. @EnableAsync
    18. @SpringBootApplication
    19. public class DemoApplication {
    20. public static void main(String[] args) {
    21. SpringApplication.run(DemoApplication.class, args);
    22. }
    23. @Slf4j
    24. @Service
    25. static class MyAsyService {
    26. @Value("${simulation.sleep.seconds:10}")
    27. private Long simSeconds;
    28. @Async
    29. CompletableFuture<String> doSomething(String req) throws InterruptedException {
    30. log.info("in service...");
    31. TimeUnit.SECONDS.sleep(simSeconds);
    32. log.info("...leaving service");
    33. return CompletableFuture.completedFuture(req + "_" + UUID.randomUUID());
    34. }
    35. }
    36. @Slf4j
    37. @Controller
    38. static class MyController {
    39. @Autowired
    40. MyAsyService service;
    41. @GetMapping("/hello")
    42. @ResponseBody
    43. CompletableFuture<String> hello(@RequestParam(required = false, defaultValue = "anonymous") String req)
    44. throws InterruptedException {
    45. log.info("in controller...");
    46. CompletableFuture<String> result = service.doSomething(req);
    47. log.info("...controller done!");
    48. return result;
    49. }
    50. }
    51. }
  • 在浏览器中:
    Controller 使用异步和 CompletableFuture 后仍然等待。

  • 在服务器日志中:

    1. ... o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
    2. ... DemoApplication$MyController : in controller.... <= req#1
    3. ... DemoApplication$MyController : ....controller done!
    4. ... DemoApplication$MyAsyService : in service....
    5. ... DemoApplication$MyAsyService : ....leaving service
    6. ... DemoApplication$MyController : in controller.... <= req#2
    7. ... DemoApplication$MyController : ....controller done!
    8. ... DemoApplication$MyAsyService : in service....
    9. ... DemoApplication$MyAsyService : ....leaving service
    10. ... DemoApplication$MyController : in controller.... <= req#3
    11. ... DemoApplication$MyController : ....controller done!
    12. ... DemoApplication$MyAsyService : in service....
    13. ... DemoApplication$MyAsyService : ....leaving service
  • (自动化、模拟 MVC) 测试(最难的部分;):

    1. package com.example.demo;
    2. import static org.hamcrest.core.StringStartsWith.startsWith;
    3. import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
    4. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
    5. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
    6. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    7. import java.util.UUID;
    8. import org.junit.jupiter.api.Test;
    9. import org.springframework.beans.factory.annotation.Autowired;
    10. import org.springframework.beans.factory.annotation.Value;
    11. import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
    12. import org.springframework.mock.web.MockHttpServletRequest;
    13. import org.springframework.test.web.servlet.MockMvc;
    14. import org.springframework.test.web.servlet.MvcResult;
    15. import org.springframework.test.web.servlet.RequestBuilder;
    16. @WebMvcTest
    17. class DemoApplicationTests {
    18. @Autowired
    19. MockMvc mockMvc;
    20. @Value("${simulation.sleep.seconds}") // -> src/test/resources/application.properties
    21. Long simSeconds;
    22. @Test
    23. void testController() throws Exception {
    24. String uid = UUID.randomUUID().toString();
    25. MvcResult mvcResult = mockMvc.perform(get("/hello?req={v}", uid)) //1.
    26. .andExpect(request().asyncStarted()) // 2.
    27. .andReturn(); // 3.
    28. mockMvc.perform(asyncDispatch(mvcResult/*, (simSeconds + 1) * 1000*/)) // 4!*
    29. .andExpectAll( // ...
    30. status().isOk(),
    31. content().string(startsWith(uid)));
    32. }
    33. // 4*: 当我们花费/模拟超过约10秒时,我们必须并且可以重新实现/丰富:
    34. static RequestBuilder asyncDispatch(MvcResult mvcResult, long timeToWait) {
    35. // 从 MockMvcRequestBuilders#asyncDispatch 复制/采用
    36. mvcResult.getAsyncResult(timeToWait);
    37. return servletContext -> {
    38. MockHttpServletRequest request = mvcResult.getRequest();
    39. request.setDispatcherType(DispatcherType.ASYNC);
    40. request.setAsyncStarted(false);
    41. return request;
    42. };
    43. }
    44. }

感谢


将服务更改为:

  1. CompletableFuture.supplyAsync(() ->{
  2. try { //...

..没有显著影响。

英文:

Thread.sleep(1000000000); seems too much for a (build) test ... That refers to ~ 12 (solar) days!

This works

  • starter

  • "App":

    1. package com.example.demo;
    2. import java.util.UUID;
    3. import java.util.concurrent.CompletableFuture;
    4. import java.util.concurrent.TimeUnit;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.beans.factory.annotation.Value;
    7. import org.springframework.boot.SpringApplication;
    8. import org.springframework.boot.autoconfigure.SpringBootApplication;
    9. import org.springframework.scheduling.annotation.Async;
    10. import org.springframework.scheduling.annotation.EnableAsync;
    11. import org.springframework.stereotype.Controller;
    12. import org.springframework.stereotype.Service;
    13. import org.springframework.web.bind.annotation.GetMapping;
    14. import org.springframework.web.bind.annotation.RequestParam;
    15. import org.springframework.web.bind.annotation.ResponseBody;
    16. import lombok.extern.slf4j.Slf4j;
    17. @EnableAsync
    18. @SpringBootApplication
    19. public class DemoApplication {
    20. public static void main(String[] args) {
    21. SpringApplication.run(DemoApplication.class, args);
    22. }
    23. @Slf4j
    24. @Service
    25. static class MyAsyService {
    26. @Value(&quot;${simulation.sleep.seconds:10}&quot;)
    27. private Long simSeconds;
    28. @Async
    29. CompletableFuture&lt;String&gt; doSomething(String req) throws InterruptedException {
    30. log.info(&quot;in service...&quot;);
    31. TimeUnit.SECONDS.sleep(simSeconds);
    32. log.info(&quot;...leaving service&quot;);
    33. return CompletableFuture.completedFuture(req + &quot;_&quot; + UUID.randomUUID());
    34. }
    35. }
    36. @Slf4j
    37. @Controller
    38. static class MyController {
    39. @Autowired
    40. MyAsyService service;
    41. @GetMapping(&quot;/hello&quot;)
    42. @ResponseBody
    43. CompletableFuture&lt;String&gt; hello(@RequestParam(required = false, defaultValue = &quot;anonymous&quot;) String req)
    44. throws InterruptedException {
    45. log.info(&quot;in controller...&quot;);
    46. CompletableFuture&lt;String&gt; result = service.doSomething(req);
    47. log.info(&quot;...controller done!&quot;);
    48. return result;
    49. }
    50. }
    51. }
  • in browser:
    Controller 使用异步和 CompletableFuture 后仍然等待。

  • in server logs:

    1. ... o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
    2. ... DemoApplication$MyController : in controller.... &lt;= req#1
    3. ... DemoApplication$MyController : ....controller done!
    4. ... DemoApplication$MyAsyService : in service....
    5. ... DemoApplication$MyAsyService : ....leaving service
    6. ... DemoApplication$MyController : in controller.... &lt;= req#2
    7. ... DemoApplication$MyController : ....controller done!
    8. ... DemoApplication$MyAsyService : in service....
    9. ... DemoApplication$MyAsyService : ....leaving service
    10. ... DemoApplication$MyController : in controller.... &lt;= req#3
    11. ... DemoApplication$MyController : ....controller done!
    12. ... DemoApplication$MyAsyService : in service....
    13. ... DemoApplication$MyAsyService : ....leaving service
  • (automatic, mock mvc) test (hardest part;):

    1. package com.example.demo;
    2. import static org.hamcrest.core.StringStartsWith.startsWith;
    3. import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
    4. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
    5. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
    6. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    7. import java.util.UUID;
    8. import org.junit.jupiter.api.Test;
    9. import org.springframework.beans.factory.annotation.Autowired;
    10. import org.springframework.beans.factory.annotation.Value;
    11. import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
    12. import org.springframework.mock.web.MockHttpServletRequest;
    13. import org.springframework.test.web.servlet.MockMvc;
    14. import org.springframework.test.web.servlet.MvcResult;
    15. import org.springframework.test.web.servlet.RequestBuilder;
    16. @WebMvcTest
    17. class DemoApplicationTests {
    18. @Autowired
    19. MockMvc mockMvc;
    20. @Value(&quot;${simulation.sleep.seconds}&quot;) // -&gt; src/test/resources/application.properties
    21. Long simSeconds;
    22. @Test
    23. void testController() throws Exception {
    24. String uid = UUID.randomUUID().toString();
    25. MvcResult mvcResult = mockMvc.perform(get(&quot;/hello?req={v}&quot;, uid)) //1.
    26. .andExpect(request().asyncStarted()) // 2.
    27. .andReturn(); // 3.
    28. mockMvc.perform(asyncDispatch(mvcResult/*, (simSeconds + 1) * 1000*/)) // 4!*
    29. .andExpectAll( // ...
    30. status().isOk(),
    31. content().string(startsWith(uid)));
    32. }
    33. // 4*: when we take/simulate more than ~10 seconds, we have to &amp; can re-implement/enrich:
    34. static RequestBuilder asyncDispatch(MvcResult mvcResult, long timeToWait) {
    35. // copied/adopted from MockMvcRequestBuilders#asyncDispatch
    36. mvcResult.getAsyncResult(timeToWait);
    37. return servletContext -&gt; {
    38. MockHttpServletRequest request = mvcResult.getRequest();
    39. request.setDispatcherType(DispatcherType.ASYNC);
    40. request.setAsyncStarted(false);
    41. return request;
    42. };
    43. }
    44. }

Thx To


Changing service to:

  1. CompletableFuture.supplyAsync(() -&gt;{
  2. try { //...

..has no significant effect.

huangapple
  • 本文由 发表于 2023年5月7日 04:43:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/76191049.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定