英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论