使用 Kotlin 协程处理阻塞代码的正确方法

10
假设我由于某些第三方库的原因有一个阻塞函数。大致如下所示:

fun useTheLibrary(arg: String): String {
   val result = BlockingLibrary.doSomething(arg)
   return result
}

BlockingLibrary.doSomething的调用应该在单独的ThreadPoolExecutor中运行。

假设有一种方法,那么使用kotlin应该如何实现?

注意:我已经阅读了这个帖子,但似乎已经过时了。

1个回答

10
如果阻塞代码是由于CPU使用而造成的,您应该使用Dispatchers.Default。如果阻塞是由于网络或磁盘问题引起的,则应使用Dispatchers.IO。您可以将此转换为挂起函数,并在withContext中包装阻塞调用,以便在从协程调用时正确挂起此函数。
suspend fun useTheLibrary(arg: String): String = withContext(Dispatchers.Default) {
   BlockingLibrary.doSomething(arg)
}

如果由于 API 要求需要使用特定的 ThreadPoolExecutor,可以使用 asCoroutineDispatcher()
val myDispatcher = myExecutor.asCoroutineDispatcher()

//...

suspend fun useTheLibrary(arg: String): String = withContext(myDispatcher) {
   BlockingLibrary.doSomething(arg)
}

如果你的库包含一种基于回调运行阻塞代码的方式,你可以使用 suspendCoroutine()suspendCancellableCoroutine() 将其转换为挂起函数。在这种情况下,你不需要担心执行者或调度程序,因为它由库的自有线程池处理。这里有一个在 Retrofit 库中的示例,他们将自己的基于回调的 API 转换为挂起函数。

1
这只是我在过去一年左右使用和阅读协程时所学到的。我建议阅读Roman Elizarov(Kotlin协程设计负责人,最近成为所有Kotlin负责人)在中等帖子上关于协程的所有文章, 但这篇文章似乎特别相关:https://elizarov.medium.com/blocking-threads-suspending-coroutines-d33e11bf4761,它略微有些陈旧,但不使用任何废弃的概念。(在你提供的留言板线程中提到的"run"函数已经过时了。) - Tenfour04
还有一个问题,您如何“取消包装”协程?也就是说,您如何从普通的非协程代码中调用挂起函数? - Pablo Fernandez
你只能从协程中调用挂起函数,因此你必须启动一个协程。 - Tenfour04
1
我是在回复你已删除的评论,只是举了 Android 作为例子。在 UI 中,通常会使用 CoroutineScopes,这些 CoroutineScopes 封装在应用程序的 UI 屏幕中。我没有做过后端,但也许有一种类似的封装模块,你可以将 CoroutineScope 附加到其中。 - Tenfour04
1
我认为是这样,尽管我认为使用单线程调度器时需要小心,因为如果在它上面运行的协程调用一个启动在同一调度器上的异步任务的挂起函数,那么可能会死锁。 不确定。 我认为 Dispatchers.Default 最少有两个线程,即使在单核机器上也是如此,出于这个原因。 - Tenfour04
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接