以某种方式启动协程时,我注意到,当它失败时,它不会使用配置的 Log4j 记录器来记录异常,而是将其记录到 stderr。尽管与现实世界的场景相去甚远,但下面的内容代表了展示该问题的最小可重现片段。
@RestController
class SoDemoController {
@GetMapping("/demo")
fun demo(): ResponseEntity<String> {
CoroutineScope(Dispatchers.Default).launch {
async {
// Something useful is done here
delay(1000)
error("some error happens")
}.await()
}
return ResponseEntity.ok("done")
}
}
输出:
Exception in thread "DefaultDispatcher-worker-3" java.lang.IllegalStateException: some error happens
...
现在,出于某种原因使用
runBlocking
解决了问题。
@RestController
class SoDemoController {
@GetMapping("/demo")
fun demo(): ResponseEntity<String> {
runBlocking {
async {
// ... rest of the code ...
输出:
2024-04-13T14:08:36.091+01:00 ERROR 5771 --- [demo] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.IllegalStateException: some error happens] with root cause
虽然我理解
runBlocking
和 CoroutineScope.launch
之间的行为是不同的,因为一个阻塞线程,另一个阻塞线程,就目前而言,它是即发即忘,但我不太明白它如何影响通道日志已完成(stderr 与 log4j)。
这种差异背后的原因是什么?解决它的最佳方法是什么?关于是否使用
runBlocking
的争论有时令人困惑,我不确定在这种情况下是否有更好的方法。
这两种情况有很大不同。
如果使用
runBlocking
,demo
函数将等待协程完成。如果协程失败,则从 demo
函数抛出异常。这会导致 500 错误,Spring 知道该异常并根据其配置记录它。
如果使用
launch
,协程会在后台启动,demo
成功返回,Spring甚至不知道后台有任务在运行。任务由协程框架管理,而不是由 Spring 管理。如果协程失败,Spring 甚至不知道这一点。错误由协程框架记录,当然,协程对 Spring 及其日志配置一无所知。协程机器根据打印到 stderr 的默认行为记录消息。
如果您想更改协程中错误的处理方式,您可以提供
CoroutineExceptionHandler
,如下所述:https://kotlinlang.org/docs/exception-handling.html#coroutineexceptionhandler