Kotlin 协程启动{} 与启动{ withContext{} }

Jef*_*hen 9 multithreading android kotlin kotlin-coroutines

我的 Android 应用程序需要在后台(在服务内)执行一些文件读/写操作,首先我使用:

CoroutineScope(Dispatchers.IO).launch {
    val fos = openFileOutput(fileName, MODE_PRIVATE)
    val oos = ObjectOutputStream(fos)
    oos.writeObject(myObj)
    oos.close()
}
Run Code Online (Sandbox Code Playgroud)

块内的每一行都有一个警告:“不适当的阻塞方法调用”

搜索完问题后,我想我明白了80%。所以基本上大多数协程只有 1 个线程,如果它被阻塞,那么该协程将没有线程去做其他工作。为了解决这个问题,我们应该withContext像这样把它包裹起来:

CoroutineScope(Dispatchers.IO).launch {
    withContext(Dispatchers.IO) {
        val fos = openFileOutput(fileName, MODE_PRIVATE)
        val oos = ObjectOutputStream(fos)
        oos.writeObject(myObj)
        oos.close()
    }
}
Run Code Online (Sandbox Code Playgroud)

Android Studio 仍然显示警告。帖子说这只是Android Studio中的一个错误,这个解决方案很好。

我不明白的是,withContext仍然在运行Dispatchers.IO。从launch块来看,它可能看起来像非阻塞,但如果Dispatchers.IO只有 1 个线程并且withContext块在该线程上运行,那么该线程仍然被阻塞,不是吗?

我还了解到Dispatchers.IO实际上有无限的线程,它只是在需要时创建一个新线程。所以withContext实际上并没有阻塞,但如果这是真的,为什么我们需要阻塞呢withContext?如果可以在需要时创建线程,因此第一个代码不会有任何问题,Dispatchers.IO因此永远不会被阻塞,对吗?

Ten*_*r04 7

是的,这是一个带有警告的错误。Lint 无法检测范围正在使用什么 Dispatcher,我想他们只是假设您使用的范围的上下文使用 Dispatchers.Main,因为这是最常见的。

您的 CoroutineScope(伪)构造函数具有 的上下文Dispatchers.IO,因此launch如果不修改它,则会继承该上下文,因此您启动的协程也使用Dispatchers.IO。所以,你的withContext块是多余的。

解决方法是在启动时指定调度程序:

CoroutineScope(Job()).launch(Dispatchers.IO) {
    val fos = openFileOutput(fileName, MODE_PRIVATE)
    val oos = ObjectOutputStream(fos)
    oos.writeObject(myObj)
    oos.close()
}
Run Code Online (Sandbox Code Playgroud)

另外,你的声明:

所以基本上大多数协程只有 1 个线程,如果它被阻塞,那么该协程将没有线程去做其他工作。

具有误导性。协程没有线程,调度程序有。有些调度程序有很多线程。


Nic*_*las 6

看来Android Studio确实存在一个bug。以下代码对我没有显示任何警告:

CoroutineScope(Dispatchers.IO).launch(Dispatchers.IO) {
    val fos = context.openFileOutput("", Context.MODE_PRIVATE)
    val oos = ObjectOutputStream(fos)
    oos.writeObject(myObj)
    oos.close()
}
Run Code Online (Sandbox Code Playgroud)

您还应该知道,此代码与您共享的两个代码之间的行为实际上没有区别。在所有三种情况下,代码都将在 IO 线程上执行。

  • CoroutineScope(context)
  • launch(context)
  • withContext(context

所有这些方法都只是指定协程上下文。默认情况下launch使用协程范围上下文,但您可以像我上面所做的那样或使用withContext.

所以基本上大多数协程只有 1 个线程,如果它被阻塞,那么该协程将没有线程去做其他工作。

Dispatchers.IO实际上默认为 64 个线程。