kotlin协程2025 通俗易懂三部曲之中篇 常用函数和并发处理
用了kt已经3年多,协程也一直在用,但是并没有好好的整体盘一遍。现在整个完全学习版,总算是又通畅了不少,加深了理解。
计划分成3篇完成讲解。
上篇,kotlin协程2025 通俗易懂三部曲之上篇 用法全解。
中篇,讲解其他重要函数用法和并发。本文。
下篇,kotlin协程2025 通俗易懂三部曲之下篇 异常处理。
1. runBlocking
它是顶级函数,它可以在任意地方使用。并不一定需要放在scope中。
它的作用就是为了卡住当前的线程来执行内部的协程任务块,执行完毕才继续往下走。
runBlocking会阻塞当前线程(主线程则会让界面卡住)。这与别的Scope(GlobalScope, MainScope, ViewModelScope…)都不同,它们不会阻塞线程。
runBlocking 示例:
logt { "Run0......" }
runBlocking {
logt { "Run1......" }
delay(3000)
launch {
logt { "run3" }
delay(1000)
logt{"run4"}
}
launch(Dispatchers.Default, start = CoroutineStart.DEFAULT) {
logt { "run5" }
delay(1000)
logt{"run6"}
}
}
logt { "Run7 end......" }
MainThread: Run0......
MainThread: Run1......
MainThread: run5
Thread[61]: run3
MainThread: run4
Thread[61]: run6
MainThread: Run7 end......
#建议
一般情况,我们不使用它。比如SharedPref,DataStore的缓存读取可以考虑使用runBlocking直接调用,一般没什么问题,当做一个同步代码块使用。
2. withContext
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T
这个挂起函数很有用:
- 自动恢复原线程并返回结果
在指定调度器(如Dispatchers.IO)执行代码块后,会自动切换回调用前的线程(如主线程),避免手动切换的复杂性。
代码块的最后一行作为返回值,直接传递给调用方。
suspend fun fetchData(): String = withContext(Dispatchers.IO) {
// 在IO线程执行网络请求
"Result"
} // 自动切回调用线程(如Dispatchers.Main)
viewModelScope.launch {
val data = fetchData()
//这里是主线程,可以直接触发更新UI操作
liveData.setValue(...)
}
- 替代
async-await的串行执行
相比async的并行任务,withContext以串行方式执行代码,无需处理await()的阻塞问题。
- 作用域继承
继承父协程的作用域(如Job和CoroutineExceptionHandler),确保异常和取消操作的传递性。 - 可取消性
内部代码块遵循协程的取消机制,响应父协程的取消请求。
3. suspendCancellableCoroutine / suspendCoroutine
public suspend inline fun <T> suspendCancellableCoroutine(
crossinline block: (CancellableContinuation<T>) -> Unit
): T
将回调式异步操作转换为挂起函数 的核心工具,相比 suspendCoroutine 额外支持 协程取消响应。
suspendCoroutine略弱不做介绍,请使用suspendCancellableCoroutine为好。
从函数签名就可以看出,他与withContext的巨大差异。
withContext的block是一个有返回值的挂起函数。因此block最后一行写上返回结果,并且整个函数也得到这个结果,并且我之前也建议把withContext block的执行,用try-catch包裹返回State。
而suspendCancellableCoroutine的block并没有返回值。而且是一个inline函数。
-
回调转挂起
将传统回调 API(如网络请求、文件 IO)封装为挂起函数,实现同步式编程风格。 -
取消支持
通过continuation.invokeOnCancellation监听协程取消事件,及时释放资源(如关闭网络连接)。 -
用法参考
三个要素:
invokeOnCancellation,resume和resumeWithExceptionsuspend fun request(url: String): Response = suspendCancellableCoroutine { continuation -> val call = OkHttpClient().newCall(Request.Builder().url(url).build()) continuation.invokeOnCancellation { call.cancel() } //及时释放资源 call.enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { continuation.resume(response) } override fun onFailure(call: Call, e: IOException) { continuation.resumeWithException(e) } }) }
4. WithTimeoutOrNull/WithTimeout
public suspend fun <T> withTimeoutOrNull(timeMillis: Long, block: suspend CoroutineScope.() -> T): T?
public suspend fun <T> withTimeout(timeout: Duration, block: suspend CoroutineScope.() -> T): T
超时后会抛出TimeoutCancellationException并自动触发invokeOnCancellation;
自动资源清理:确保网络请求等资源不会泄漏;
仍保持结构化并发特性,异常会向上传播;
示例:
suspend fun requestWithTimeout(
url: String,
timeoutMillis: Long = 5000L
): Response? = withTimeoutOrNull(timeoutMillis) {
suspendCancellableCoroutine { continuation ->
val call = OkHttpClient().newCall(Request.Builder().url(url).build())
continuation.invokeOnCancellation { call.cancel() }
call.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(response)
}
override fun onFailure(call: Call, e: IOException) {
continuation.resumeWithException(e)
}
})
}
}
5. coroutineScope & supervisorScope
他们都是挂起函数。只能在协程块中运行。
-
coroutineScope
创建一个协程作用域并且在里面执行协程代码块。这个Scope继承外面scope的coroutineContext,但是重写它的Job。
这个函数设计是为了并行分解工作。当任一协程失败,整个scope就失败,剩下的协程就会被cancel。当所有的子协程完成后才会返回。 -
supervisorScope
创建一个协程作用域并且在里面执行协程代码块。这个Scope继承外面scope的coroutineContext,但是重写它的Job为SupervisorJob。当所有的子协程完成后才会返回。与coroutineScope不同, 一个子协程失败,并不会让整个scope去失败而且不会影响其他子协程,所以你要自己处理错误。
常用的viewModelScope,MainScope,lifecycleScope默认都是这种行为。
理解 coroutineScope 和 supervisorScope 之间的使用场景:
当你希望所有子协程因一个任务的失败而一同失败时,使用 coroutineScope;
当你想独立处理子协程的失败并保持其他操作运行时,使用 supervisorScope。
subScope.launch {
coroutineScope { // 父协程
launch { // 子协程A
throw Exception("协同作用域异常") // ①异常触发
}
launch { // 子协程B
delay(1000)
println("永远不会执行") // ②被级联取消
}
}
}
supervisorScope { // 父协程
launch { // 子协程A
throw Exception("主从作用域异常") // ①异常被隔离
}
launch { // 子协程B
delay(1000)
println("正常执行") // ②不受影响
}
}// ③异常不会传播给父协程
这里的作用,其实跟上篇链接 6. SupervisorJob() vs Job() 是差不多的。Job()属于给Scope定义约束。而本章节2个函数,是在比如ViewModel中使用的时候,不方便或者没必要创建自定义Scope,那么就可以通过这2个函数来帮助我们进行并发。
异常处理在【下篇】。
6. Scope的最佳实践
android已经提供了各种场景的Scope,而且自定义也只是调用cancel()而已。
-
- 全局使用,使用GlobalScope(官方不推荐)或者如第4条
-
- activity|fragment类中使用lifecycleScope
-
- viewModel类中使用viewModelScope
-
- 某个类(比如单例类)定义全局通用Scope:
val scope = MainScope() val backgroundScope by lazy { CoroutineScope(Dispatchers.IO) } val backgroundScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) //在自定义关闭的时候,调用 scope.cancel()
注意:避免到处传递scope,把Scope当做参数传递是不好的编程习惯。对于业务类,我们应该只提供suspend函数。scope交给调用者自行决策。
源码scope自动cancel原理
一般情况我们不需要自定义Scope。
查看MainScope的源码:
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
查看lifecycleScope的源码:
//重定义
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
//继续追踪,是创建了LifecycleCoroutineScopeImpl
public val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
...
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main.immediate //运行在主线程中。
)
...
}
//父类为这样定义的。基本上啥也没有,就是CoroutineScope的子类,放了一个lifecycle我们传入的
public actual abstract class LifecycleCoroutineScope internal actual constructor() :
CoroutineScope {
internal actual abstract val lifecycle: Lifecycle
...
}
internal class LifecycleCoroutineScopeImpl(
override val lifecycle: Lifecycle,
override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
init {
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
coroutineContext.cancel()
}
}
fun register() {
launch(Dispatchers.Main.immediate) {
if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
} else {
coroutineContext.cancel() //取消
}
}
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
lifecycle.removeObserver(this)
coroutineContext.cancel() //取消
}
}
}
可以看出,其实就是就是实现了CoroutineScope的子类。通过监听lifecycle的过程来自动cancel,仅此而已。
我们可以类推到viewModelScope,基于想要监听的生命周期而自动cancel。
7. 并发处理
根据是依赖顺序还是并发分为3种:
7.1 顺序执行
通过withContext或者async+await等待前一个任务完成,接着进行第二个任务;
//withContext
lifecycleScope.launch {
//step1
val data = withContext(Dispatchers.IO) { //切换线程
...
""" {"data":"request successfully."} """
}
....
//do step2...
}
//async await
lifecycleScope.launch {
val deferred = async(Dispatchers.IO) {
...
""" {"data":"request successfully."} """
}
val data = deferred.await()
//do step2...
}
7.2 并发后统一等待
lifecycleScope.launch {
ALogJ.t("start...")
val deferred1 = async {
delay(1000)
"1111"
}
val deferred2 = async {
delay(800)
"2222"
}
val data1 = deferred1.await()
val data2 = deferred2.await()
//等待他们完成
ALogJ.t("data1 $data1, data2 $data2")
}
//执行结果:
6.057 MainThread: start...
17.068 MainThread: data1 1111, data2 2222
可以看到async是立刻执行的,时间只有1秒。同时,所有Deferred.await以后,可以统一等待执行并发完成。这也能看出withContext和async、await的区别。
7.3 纯并发
- 可以使用上面7.2的async 不用await即可;
- 可以使用上面7.2的async 修改成launch即可;
- 推荐使用5. coroutineScope & supervisorScope章节2个函数来完成:
a. 使用coroutineScope来让任意异常取消整个执行;
b. supervisorScope来让异常只隔离在单个子线程中。但是我仍然建议异常处理自己try-catch。
参考:
subScope.launch {
coroutineScope { // 父协程
launch { // 子协程A
throw Exception("协同作用域异常") // ①异常触发
}
launch { // 子协程B
delay(1000)
println("永远不会执行") // ②被级联取消
}
}
}
supervisorScope { // 父协程
launch { // 子协程A
throw Exception("主从作用域异常") // ①异常被隔离
}
launch { // 子协程B
delay(1000)
println("正常执行") // ②不受影响
}
}// ③异常不会传播给父协程
总结
这篇文章是Kotlin协程三部曲的中篇,主要讲解协程的重要函数用法和并发处理。文章介绍了协程函数:
runBlocking, withContext, suspendCancellableCoroutine, withTimeoutOrNull/withTimeout, coroutineScope和supervisorScope等。并介绍了如何根据场景使用哪种并发处理。
更多推荐

所有评论(0)