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

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

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&lt;User&gt; createUser(@RequestBody User user) {
        System.out.println(Thread.currentThread().getId()+&quot;-----------------------------------&quot;+System.currentTimeMillis()+&quot;----------&quot;+&quot;inside controller&quot;);
        return userService.createUser(user);
    }

// service

   @Override
    @Async(&quot;asyncExecutor&quot;)
    public CompletableFuture&lt;User&gt; createUser(User user) {
        System.out.println(Thread.currentThread().getId()+&quot;-----------------------------------&quot;+System.currentTimeMillis()+&quot;-------------inside services&quot;);
        return CompletableFuture.supplyAsync(() -&gt;{
            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(太阳)天!

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

  • starter

  • "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;
        }
      }
    }
    
  • 在浏览器中:
    Controller 使用异步和 CompletableFuture 后仍然等待。

  • 在服务器日志中:

    ... 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;
      	};
      }
    }
    

感谢


将服务更改为:

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

..没有显著影响。

英文:

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

This works

  • starter

  • "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(&quot;${simulation.sleep.seconds:10}&quot;)
        private Long simSeconds;
    
        @Async
        CompletableFuture&lt;String&gt; doSomething(String req) throws InterruptedException {
          log.info(&quot;in service...&quot;);
          TimeUnit.SECONDS.sleep(simSeconds);
          log.info(&quot;...leaving service&quot;);
          return CompletableFuture.completedFuture(req + &quot;_&quot; + UUID.randomUUID());
        }
      }
    
      @Slf4j
      @Controller
      static class MyController {
        @Autowired
        MyAsyService service;
    
        @GetMapping(&quot;/hello&quot;)
        @ResponseBody
        CompletableFuture&lt;String&gt; hello(@RequestParam(required = false, defaultValue = &quot;anonymous&quot;) String req)
            throws InterruptedException {
          log.info(&quot;in controller...&quot;);
          CompletableFuture&lt;String&gt; result = service.doSomething(req);
          log.info(&quot;...controller done!&quot;);
          return result;
        }
      }
    }
    
  • in browser:
    Controller 使用异步和 CompletableFuture 后仍然等待。

  • in server logs:

    ... o.s.web.servlet.DispatcherServlet    : Completed initialization in 1 ms
    ... DemoApplication$MyController : in controller.... &lt;= req#1
    ... DemoApplication$MyController : ....controller done!
    ... DemoApplication$MyAsyService : in service....
    ... DemoApplication$MyAsyService : ....leaving service
    ... DemoApplication$MyController : in controller.... &lt;= req#2
    ... DemoApplication$MyController : ....controller done!
    ... DemoApplication$MyAsyService : in service....
    ... DemoApplication$MyAsyService : ....leaving service
    ... DemoApplication$MyController : in controller.... &lt;= 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(&quot;${simulation.sleep.seconds}&quot;) // -&gt; src/test/resources/application.properties
      Long simSeconds;
    
      @Test
      void testController() throws Exception {
        String uid = UUID.randomUUID().toString();
        MvcResult mvcResult = mockMvc.perform(get(&quot;/hello?req={v}&quot;, 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 &amp; can re-implement/enrich:
      static RequestBuilder asyncDispatch(MvcResult mvcResult, long timeToWait) {
    
      	// copied/adopted from MockMvcRequestBuilders#asyncDispatch
      	mvcResult.getAsyncResult(timeToWait);
    
      	return servletContext -&gt; {
      		MockHttpServletRequest request = mvcResult.getRequest();
      		request.setDispatcherType(DispatcherType.ASYNC);
      		request.setAsyncStarted(false);
      		return request;
      	};
      }
    }
    

Thx To


Changing service to:

CompletableFuture.supplyAsync(() -&gt;{
  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:

确定