您应该使用结构化并发适当地确定并发范围。如果您不这样做,您的协程可能会泄漏。在您的情况下,将它们限定为处理单个消息似乎是合适的。
这是一个例子:
/* I don't know Kafka, but let's pretend this function gets
* called when you receive a new message
*/
suspend fun onMessage(msg: Message) {
val ids: List<Int> = msg.getIds()
val jobs = ids.map { id ->
GlobalScope.launch { restService.post(id) }
}
jobs.joinAll()
}
如果对restService.post(id) 的调用之一因异常而失败,该示例将立即重新抛出异常,并且所有尚未完成的作业都会泄漏。他们将继续执行(可能无限期地),如果他们失败了,你就不会知道了。
要解决这个问题,您需要确定协程的范围。这是没有泄漏的相同示例:
suspend fun onMessage(msg: Message) = coroutineScope {
val ids: List<Int> = msg.getIds()
ids.forEach { id ->
// launch is called on "this", which is the coroutineScope.
launch { restService.post(id) }
}
}
在这种情况下,如果对restService.post(id) 的调用之一失败,则协程范围内的所有其他未完成的协程都将被取消。当你离开作用域时,你可以确定你没有泄露任何协程。
另外,因为coroutineScope 会等到所有子协程完成,你可以放弃jobs.joinAll() 调用。
旁注:
编写启动一些协程的函数时的约定是让调用者使用接收器参数决定协程范围。使用onMessage 函数执行此操作可能如下所示:
fun CoroutineScope.onMessage(msg: Message): List<Job> {
val ids: List<Int> = msg.getIds()
return ids.map { id ->
// launch is called on "this", which is the coroutineScope.
launch { restService.post(id) }
}
}