coroutineScope和supervisorScope的区别

本文介绍了Kotlin中coroutineScope和supervisorScope的区别,主要在于异常处理方式。coroutineScope中子协程异常会导致整体取消,而supervisorScope则允许子协程独立异常。通过launch和async发起的协程示例,详细阐述了两者在异常发生时的不同行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 区别

coroutineScopesupervisorScope都是用来创建一个 CoroutineScope并执行代码块,创建的 CoroutineScope将继承上一级 CoroutineScopeCoroutineScope.coroutineContext,但是会重写 croutineContextJob

它们的区别在于 coroutineScopecoroutine 是一个 ScopeCoroutine,而 supervisorScopecoroutine是一个 SupervisorCoroutineSupervisorCoroutine继承自 ScopeCoroutine但是重写了其父类 JobSupport的方法 childCancelled(cause: Throwable): Boolean并返回 false。这就是它们之间的唯一区别。

这一区别导致:

  • coroutineScope中,只要任意一个子协程发生异常,整个 scope都会执行失败,并且其余的所有子协程都会被取消掉;
  • supervisorScope中,一个子协程的异常不会影响整个 scope的执行,也不会影响其余子协程的执行;

这两种 CoroutineScope创建方式的区别只在于当发生异常时,它们对异常的处理方式各不相同。

我们接下来用例子详细说明它们各自如何处理异常。


2. 举例说明

若想统一处理协程的异常,防止异常引发程序崩溃,可以给协程设置 CoroutineExceptionHandler,我们先定义一个协程异常处理器,如下,简单地把异常信息打印出来:

private val exceptionHandler = CoroutineExceptionHandler{ _, e ->    
    e.message?.let { Log.e("crx", "异常信息: $it") }
} 

我们在 ActivitylifecycleScope中来执行测试代码:

lifecycleScope.launch(exceptionHandler) {    
    //
    ... ...
    //
    Log.e("crx", "在执行完了Scope之后")
}

后面的测试代码,就是添加在上面省略号的位置。

2.1 launch发起的协程

我们分别定义两个方法,如下:

private suspend fun testSupervisorScope() = supervisorScope {    
    launch { throw IllegalArgumentException("随便抛一个异常") }    
    launch {        
        delay(1000)        
        Log.e("crx", "另一个协程")
    }
}

private suspend fun testCoroutineScope() = coroutineScope {    
    launch { throw IllegalArgumentException("随便抛一个异常") }    
    launch {        
        delay(1000)        
        Log.e("crx", "另一个协程")    
    }
}

这两个方法唯一的区别在于一个是 supervisorScope,一个是 coroutineScope
分别执行这两个测试方法,打印结果如下:

//执行testSupervisorScope方法打印的结果
E/crx: 异常信息: 随便抛一个异常
E/crx: 另一个协程
E/crx: 在执行完了Scope之后

//执行testCoroutineScope方法打印的结果
E/crx: 异常信息: 随便抛一个异常

可以得出以下结论

launch发起的协程,对于 supervisorScope,即使有一个子协发生了异常,其余子协程仍然可以正常执行,且整个 scope都可以正常执行;而对于 coroutineScope,一旦有某个子协程发生异常,其余子协程将被取消,且会导致整个 scope执行失败。

2.2 async发起的协程

测试代码和2.1完全一样,唯一不同是把 launch换成了 async,如下:

private suspend fun testSupervisorScope() = supervisorScope {    
    async { throw IllegalArgumentException("随便抛一个异常") }    
    async {        
        delay(1000)        
        Log.e("crx", "另一个协程")
    }
}

private suspend fun testCoroutineScope() = coroutineScope {    
    async { throw IllegalArgumentException("随便抛一个异常") }    
    async {        
        delay(1000)        
        Log.e("crx", "另一个协程")    
    }
}

分别执行,打印结果如下:

//执行testSupervisorScope方法打印的结果
E/crx: 另一个协程
E/crx: 在执行完了Scope之后

//执行testCoroutineScope方法打印的结果
E/crx: 异常信息: 随便抛一个异常

可以看到,在 supervisorScope中,async并没有抛异常,另一个子协程顺利执行,整个 scope也执行完毕;在 croutineScope中,执行结果和 launch的执行结果一样。

如果调用 asyncawait方法,supervisorScope则会抛异常,但仍然不会影响其余子协程的执行,相关示例这里就不展示了。

结论

async方法启动的协程,对于 supervisorScope,如果不调用 asyncawait方法,协程就不会抛异常,如果调用,则会抛异常,但不管是否抛异常,其余子协程和整个 scope都可正常执行;对于 coroutineScope,只要某个子协程发生异常,就会影响整个 scope和其余子协程的执行。

协程中,可以使用协程作用域中的 `cancel()` 函数来取消一个协程。当协程被取消时,它会抛出一个 `CancellationException` 异常。如果在协程中捕获了这个异常,那么协程的执行会立即结束。 然而,在某些情况下,我们可能不想让协程被取消,或者不想让它在取消时抛出异常。这时候我们就可以使用 `SupervisorJob` `SupervisorScope` 来管理协程的取消操作。 `SupervisorJob` 是一个特殊的 `Job` 类型,它会将所有子协程放入一个单独的协程作用域中,从而使它们互相独立。当一个子协程被取消时,其它子协程不会受到影响。同时,如果使用 `SupervisorScope` 来启动子协程,则在取消时也不会抛出 `CancellationException` 异常。 下面是一个例子,展示了如何在协程中使用 `SupervisorJob` `SupervisorScope`: ```kotlin import kotlinx.coroutines.* fun main() = runBlocking { val supervisor = SupervisorJob() val scope = CoroutineScope(coroutineContext + supervisor) val job1 = scope.launch { println("Child coroutine 1 started") delay(1000) println("Child coroutine 1 completed") } val job2 = scope.launch { println("Child coroutine 2 started") try { delay(3000) println("Child coroutine 2 completed") } catch (e: CancellationException) { // do nothing } } delay(2000) job1.cancel() job2.cancel() supervisor.children.forEach { it.join() } println("All coroutines completed") } ``` 在这个例子中,我们首先创建了一个 `SupervisorJob`,然后使用它来创建一个协程作用域。在协程作用域中,我们创建了两个子协程 `job1` `job2`。在 `job1` 中,我们没有使用 `SupervisorScope`,所以在取消时会抛出 `CancellationException` 异常。而在 `job2` 中,我们使用了 `SupervisorScope`,所以在取消时不会抛出异常。 最后,我们通过遍历 `supervisor.children` 来等待所有子协程执行完毕,并输出 "All coroutines completed"。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值