使用 withContext 串行切线程
我们知道启动一个协程是使用 CoroutineScope 的 launch 启动,launch 开启的是并行的切线程:
CoroutineScope(Dispatchers.Main).launch {
println("Test: 1 - launch + ${Thread.currentThread().name}")
// 开启了另一个协程,不会等待这个协程执行完就会继续执行下一行代码
launch(Dispatchers.IO) {
Thread.sleep(2000)
println("Test: 2 - withContext + ${Thread.currentThread().name}")
}
println("Test: 3 - launch + ${Thread.currentThread().name}")
}
输出结果:
Test: 1 - launch + main
Test: 3 - launch + main
Test: 2 - withContext + DefaultDispatcher-worker-2
上面是在主线程启动了一个协程,然后在它内部也开启了一个协程,因为并行的关系,内部的协程调用 launch 后会直接执行下面的代码,并不会等待内部协程运行完才执行后面的代码。
很多时候我们还会需要串行的切线程,即希望启动的协程能等到执行结束后再执行后面的代码,这时候就需要 withContext。
withContext 是一个挂起函数,所以它只能在协程或另一个挂起函数上运行;我们使用 withContext 实现串行代码执行:
CoroutineScope(Dispatchers.Main).launch {
println("Test: 1 - launch + ${Thread.currentThread().name}")
withContext(Dispatchers.IO) {
Thread.sleep(2000)
println("Test: 2 - withContext + ${Thread.currentThread().name}")
}
println("Test: 3 - launch + ${Thread.currentThread().name}")
}
输出结果:
Test: 1 - launch + main
Test: 2 - withContext + DefaultDispatcher-worker-2
Test: 3 - launch + main
在实际项目中也很常用,比如先网络请求拿到数据,然后处理数据,最后显示在界面,就可以这么写:
viewModelScope.launch {
val data = withContext(Dispatchers.IO) {
// 网络请求
"data"
}
val processedData = withContext(Dispatchers.Default) {
// 处理数据
"processed $data"
}
println("Processed data: $processedData")
}
withContext 从名字来看是 [我要基于我给出的 Context 来执行代码] 的意思,这里的 Context 是指的 CoroutineContext,也就是说 withContext 是用来临时切换 CoroutineContext 的而不只是切线程。
自定义挂起函数
我们都知道用 suspend 关键字声明一个函数就是挂起函数,那我们什么时候会需要这样自定义一个挂起函数呢?一般是在我们项目代码中用到了挂起函数,因为挂起函数只能在另一个挂起函数调用,不然 IDE 就会报错给我们提示:
所以自定义挂起函数正确的方式是:我需要一个挂起函数,因为代码需要用到别的挂起函数,所以我需要给函数加上 suspend 关键字声明为挂起函数。声明为挂起函数会限制函数的使用范围,就要遵循 kotlin 的规则来使用。
并不存在 [挂起函数怎么写] 这个想法,而是你在写自定义函数的时候,如果里面会用到别的挂起函数,IDE 给我们提示要加上,那就给这个函数加上 supsend;如果函数代码内部没有调用挂起函数,那就不要加 suspend 声明。这个行为是半自动的有 IDE 辅助。
总结
-
串行的切线程使用 withContext,实际上 withContext 是用来临时切换 CoroutineContext 的而不只是切线程
-
自定义挂起函数正确的方式是:我需要一个挂起函数,因为代码需要用到别的挂起函数,所以我需要给函数加上 suspend 关键字声明为挂起函数