协程解决之前的响应,kotlin

Response before coroutine resolved, kotlin

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

@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"
}

}

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

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

Side note: this is actually why coroutineScope is suspending. It's actually the suspending equivalent of runBlocking, which on the other hand blocks the current thread while waiting for child coroutines.

你需要的是一个比你的函数体更大的作用域。一种肮脏的方法是使用 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 在这种情况下仍然不是一个好主意,因为它会阻止您集中更改“全局”协程的协程上下文元素。例如,拥有应用程序范围允许您添加异常处理程序、更改线程池或提供协程名称。