【问题标题】:Response before coroutine resolved, kotlin协程解析前的响应,kotlin
【发布时间】:2021-09-08 14:55:44
【问题描述】:

你好堆栈溢出! 我正在编写一个小型服务网络应用程序,它接受使用 Spring Boot kotlin 发送电子邮件的数据。我的问题是如何让控制器在收到请求后立即响应并在后台线程中发送实际的电子邮件。首先控制器发送电子邮件,然后在rabbitMQ上发布消息,然后才返回响应作为指定字符串。

@RestController
class MailController(private val emailService: EmailService, private val msgSender: CustomMessageSender) {

@PostMapping("/api/sendemail")
suspend fun sendEmail(@RequestBody request: Email): String {
    coroutineScope {
        launch(Dispatchers.IO) {
            try {
                emailService.sendMail(request.to, request.subject!!, request.msg)
                delay(2000)
                msgSender.sendMessage()
            } catch (e: Exception) {
                println(e)
            }
        }
    }
    return "email queued"
}

}

【问题讨论】:

  • 这应该有效。尝试async 而不是launch
  • @sidgate 不,它不应该与coroutineScope 一起使用,因为这个函数会等到子协程完成后再恢复

标签: spring-boot kotlin kotlin-coroutines coroutine


【解决方案1】:

对于这种需求,你需要了解CoroutineScopes。 作用域限制了协程的生命周期。

当您使用coroutineScope { ... } 时,您定义了一个将在块末尾结束的范围。这意味着,在此功能可以恢复之前,在其中启动的所有子协程必须已完成或已取消。换句话说,coroutineScope 将暂停当前协程,直到所有子协程都完成,这不是你想要的,因为在发送电子邮件之前你不会返回。

旁注:这实际上是coroutineScope 暂停的原因。它实际上是 runBlocking 的挂起等价物,另一方面,它在等待子协程时阻塞当前线程。

你需要的是一个比你的函数体更大的范围。一种肮脏的方法是使用GlobalScope(与您的应用程序具有相同的生命周期)。更好的方法是定义一个与 Spring 组件的生命周期相关的协程范围,这样如果你的组件被销毁,它启动的协程就会被取消。

执行上述操作的一种方法是定义这样的范围:

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import javax.annotation.PreDestroy

@RestController
class MailController(
    private val emailService: EmailService,
    private val msgSender: CustomMessageSender
) {
    // use a regular Job() if you want all coroutines to be cancelled if one fails
    private val coroutineScope = CoroutineScope(SupervisorJob())

    // this method is NOT suspending anymore, because it doesn't call
    // suspending functions directly, it just starts a coroutine.
    @PostMapping("/api/sendemail")
    fun sendEmail(@RequestBody request: Email): String {
        coroutineScope.launch(Dispatchers.IO) {
            try {
                emailService.sendMail(request.to, request.subject!!, request.msg)
                delay(2000)
                msgSender.sendMessage()
            } catch (e: Exception) {
                println(e)
            }
        }
        return "email queued"
    }
    

    @PreDestroy
    fun cancelScope() {
        coroutineScope.cancel()
    }
}

如果您的组件始终与您的应用程序一样长,您也可以在应用程序级别定义自定义范围,并将其注入此处。 GlobalScope 在这种情况下仍然不是一个好主意,因为它会阻止您为“全局”协程集中更改协程上下文的元素。例如,拥有应用程序范围允许您添加异常处理程序、更改线程池或指定协程名称。

【讨论】:

    猜你喜欢
    • 2020-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多