在Android中,我通常会看到一些这样的挂起函数的代码片段:
viewModelScope.launch { //implemented by Person1
A()
}
//implemented by Person2
suspend fun A() {
B()
}
//implemented by Person3
suspend fun B() {
C()
}
//implemented by Person4
suspend fun C() {
doSomething()
}
如果Person3可以像下面这样写让C()在不同的线程上运行
suspend fun B() {
withContext(Dispatchers.IO) {
C()
}
}
负责A()的Person2也想,既然他正在调用B的挂起函数,那么他需要让B在一个线程上运行。
这可能会创建多个线程,这是没有必要的,那么如何让Person1和Person2知道他调用的挂起函数已经在不同的线程上运行了。
假设
doSomething
访问文件系统。然后该函数负责将这些部分移至 IO 调度程序上。该函数可能如下所示:
suspend fun doSomething() = withContext(Dispatchers.IO) {
// accesses the file system here
}
doSomething
(函数C
)的调用者不需要了解文件系统访问以及该函数在哪个调度程序上执行。它可以简单地调用 doSomething()
一切都很好。
现在,假设
B
决定像这样调用 C
:
withContext(Dispatchers.IO) {
C()
}
当前协程将从当前线程移动到 IO 调度程序的线程,并调用
C
。 C
然后调用 doSomething
,该函数现在也执行 withContext(Dispatchers.IO)
。但协程已经在该调度程序上,因此不会发生额外的线程切换。然后,发生文件系统访问。在 IO 调度程序的线程上,正如 B
以及 doSomething
所确定的那样。withContext
不会创建占用新线程的新协程。相反,
current协程的配置方式是在适当的线程上运行 - 但如果它已经is在正确的线程上运行,则不会发生任何变化,不会占用新线程。您可以多次调用
withContext(Dispatchers.IO)
,只有第一次真正有效。这与使用 launch
或
async
启动新协程不同。这很可能导致调度程序创建一个新线程(或重用线程池中的一个线程)。但这就是挂起函数的用途:当您已经处于挂起函数中时,不需要新的协程来调用下一个挂起函数。不涉及新线程。总而言之,B
切换到IO调度程序没有必要,但也没有坏处。但是 doSomething
和
C
的文档可以确认内部结构确保文件系统访问转移到 IO 调度程序。这样 B
就不需要猜测所有事情是否都正确处理,以及 B
是否应该(只是为了确保)将当前协程移动到 IO 调度程序本身。