coroutineScope和supervisorScope的区别

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和其余子协程的执行。


版权声明:本文为chenrenxiang原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。