我是科特林合作公司的新手,我试着理解监管。正如医生们所说:
子女的失败或取消不会导致主管职务失败,也不会影响其其他子女。
好的,我为JVM编写了以下代码:
@JvmStatic
fun main(args: Array<String>) = runBlocking {
val supervisorScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
// Coroutine #1
supervisorScope.launch {
println("Coroutine #1 start")
delay(100)
throw RuntimeException("Coroutine #1 failure")
}
// Coroutine #2
supervisorScope.launch {
for (i in 0 until 5) {
println("Coroutine #2: $i")
delay(100)
}
}
supervisorScope.coroutineContext[Job]!!.children.forEach { it.join() }
}
这里一切都很好,Coroutine #1
失败既不影响父级,也不影响Coroutine #2
。这就是监督的目的。输出与文档一致:
Coroutine #1 start
Coroutine #2: 0
Coroutine #2: 1
Exception in thread "DefaultDispatcher-worker-1" java.lang.RuntimeException: Coroutine #1 failure
at supervisor.SupervisorJobUsage$main$1$1.invokeSuspend(SupervisorJobUsage.kt:16)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)
Coroutine #2: 2
Coroutine #2: 3
Coroutine #2: 4
Process finished with exit code 0
但是,我为Android编写了几乎相同的代码:
class CoroutineJobActivity : AppCompatActivity() {
private val TAG = "CoroutineJobActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
testSupervisorScope()
}
private fun testSupervisorScope() {
// Coroutine #1
lifecycleScope.launch(Dispatchers.Default) {
Log.d(TAG, "testSupervisorScope: Coroutine #1 start")
delay(100)
throw RuntimeException("Coroutine #1 failure")
}
// Coroutine #2
lifecycleScope.launch(Dispatchers.Default) {
for (i in 0 until 5) {
Log.d(TAG, "testSupervisorScope: Coroutine #2: $i")
delay(100)
}
}
}
}
这个输出是意外的,因为Coroutine #2
由于应用程序崩溃而没有完成它的工作。
testSupervisorScope: Coroutine #1 start
testSupervisorScope: Coroutine #2: 0
testSupervisorScope: Coroutine #2: 1
testSupervisorScope: Coroutine #2: 2
FATAL EXCEPTION: DefaultDispatcher-worker-2
Process: jp.neechan.kotlin_coroutines_android, PID: 23561
java.lang.RuntimeException: Coroutine #1 failure
at jp.neechan.kotlin_coroutines_android.coroutinejob.CoroutineJobActivity$testSupervisorScope$1.invokeSuspend(CoroutineJobActivity.kt:25)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)
虽然lifecycleScope.coroutineContext
是SupervisorJob() + Dispatchers.Main.immediate
,但我在这里看到,子协同的失败影响了父母和其他孩子。
那么监督lifecycleScope
的目的是什么呢?
发布于 2020-02-05 14:36:48
在您的用例中,很少有东西起着重要的作用。
这里一切都很好,Coroutine #1的失败不影响父母,也不影响Coroutine #2。这就是监督的目的。
CoroutineExceptionHandler是默认的处理程序,一旦异常被coroutine抛出,它将打印异常细节。使用launch
和加入将迫使协同器等待作业完成,所以这就是为什么您能够看到这两个协同器的输出。
现在,如果一个协同线与加入崩溃,那么它将抛出CancellationException
特别是,它意味着
a parent coroutine invoking join on a
child coroutine that was started using launch(coroutineContext) { ...
} builder throws CancellationException if the child had crashed
,除非在上下文中安装了一个非标准的CoroutineExceptionHandler。
不加入CoroutineExceptionHandler :默认情况下,CoroutineExceptionHandler将忽略CancellationException
,如果不使用join
,则不会打印任何内容。
CoroutineExceptionHandler和join:如果在coroutine上使用加入,那么构建器将抛出CancellationException
,并且由于作业尚未完成(其他协同工作仍在进行中),那么它将打印错误并继续执行其他作业。
supervisorScope.coroutineContext[Job]!!.children.forEach { it.join() }
遵循与异常传播相同的行为定义,其中GlobalScope没有关联的Job
对象。
在安卓系统中,Thread.uncaughtExceptionHandler是默认的处理程序,它会在异常情况下关闭应用程序,并显示崩溃对话框。
这就是在不同的生态系统中处理带有或不使用join
的异常之间的区别,因此在使用join
的kotlin测试中没有终止行为(这不是安卓应用程序中的)。
虽然lifecycleScope.coroutineContext是SupervisorJob() + Dispatchers.Main.immediate,但是在这里我看到了子协同机制的失败影响了父级和其他子级。
uncaughtExceptionHandler
,这将杀死应用程序(参见第1点)。所以,您可以使用withContext
lifecycleScope.launch(Dispatchers.Default) {
for (i in 0 until 5) {
Log.d(TAG, "testSupervisorScope: Coroutine #1: $i")
delay(100)
}
try {
// can use another context to change thread, e.g Dispatchers.IO
withContext(lifecycleScope.coroutineContext) {
Log.d(TAG, "testSupervisorScope: Coroutine withContext start")
delay(100)
throw RuntimeException("Coroutine sub-task failure")
}
} catch (e: java.lang.RuntimeException) {
e.printStackTrace()
}
}
或者,为了建立父-子关系,使用相同的作用域调用子协同。
private fun testSupervisorScope() = runBlocking {
// Coroutine #1
lifecycleScope.launch(Dispatchers.Default) {
for (i in 0 until 5) {
Log.d(TAG, "testSupervisorScope: Coroutine #1: $i")
delay(100)
}
// Coroutine child #1
try {
childCoroutineWithException().await()
} catch (e: Exception) {
Log.d(TAG, "caught exception")
e.printStackTrace()
}
}
}
// Note: use same scope `lifecycleScope` to ceate child coroutine to establish parent-child relation
fun childCoroutineWithException(): Deferred<String> = lifecycleScope.async {
Log.d(TAG, "testSupervisorScope: Coroutine child #1 start")
delay(100)
throw RuntimeException("Coroutine child #1 failure")
}
一旦建立了父-子关系,那么上面的代码将能够处理catch
块中的异常,并且不会影响其他子协同执行。
与儿童合作的结果:
CoroutineJobActivity: testSupervisorScope: Coroutine #1: 1
CoroutineJobActivity: testSupervisorScope: Coroutine #1: 2
CoroutineJobActivity: testSupervisorScope: Coroutine #1: 3
CoroutineJobActivity: testSupervisorScope: Coroutine #1: 4
CoroutineJobActivity: testSupervisorScope: Coroutine #1: 5
CoroutineJobActivity: testSupervisorScope: Coroutine child #1 start
CoroutineJobActivity: Coroutine child #1 failure
您可以通过删除runBlocking
来进一步简化示例
private fun testSupervisorScope(){
// Coroutine #1
lifecycleScope.launch(Dispatchers.Default) {
for (i in 0 until 5) {
Log.d(TAG, "testSupervisorScope: Coroutine #1: $i")
try {
childCoroutineWithException().await()
} catch (e: Exception) {
Log.d(TAG, "caught exception")
e.printStackTrace()
}
delay(100)
}
}
}
// Note: use same scope `lifecycleScope` to ceate child coroutine to establish parent-child relation
fun childCoroutineWithException(): Deferred<String> = lifecycleScope.async {
Log.d(TAG, "testSupervisorScope: Coroutine child #1 start")
delay(100)
throw RuntimeException("Coroutine child #1 failure")
}
为了避免应用程序崩溃,您可以实现自己的处理程序,以避免应用程序崩溃(除非您真的需要,否则不要这样做,因为这是错误的做法,导致了技术债务)。
发布于 2020-02-05 17:17:07
如果你仔细看看你的输出:
Exception in thread "DefaultDispatcher-worker-1" java.lang.RuntimeException: Coroutine #1 failure
at supervisor.SupervisorJobUsage$main$1$1.invokeSuspend(SupervisorJobUsage.kt:16)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)
这是JVM级未处理异常处理程序的报告.这意味着,即使没有取消范围的作业,异常也会扼杀Java线程。执行器可以很容易地从这些错误中恢复过来,但是Android使用了不同的异常处理程序,它会立即杀死整个应用程序。有关协同作用域的任何内容都不会改变这种行为。
下面是一些代码,您可以尝试查看该机制的运行情况:
GlobalScope.launch(Dispatchers.Default) {
Thread.currentThread().setUncaughtExceptionHandler { thread, exception ->
Log.e("MyTag", "We got an error on ${thread.name}: $exception")
}
throw RuntimeException("Dead")
}
如果我评论一下setUncaughtExceptionHandler
的调用,我就会像你一样出现应用程序崩溃。但这样的话,我就能在日志里找到一条线了。
当然,您不会在生产中这样写,但是如果在作用域中添加一个协同异常处理程序,它将产生同样的效果。
然而,整个故事对我来说没有多大意义,而且我认为一般情况下的异常处理仍然是一个需要在Kotlin协同工作中改进的领域。
发布于 2020-02-05 13:38:11
问题是,SupervisorJob
不像您所期望的那样工作。SupervisorScope
的思想是,当一个异常由它的一个子程序启动时,它不会取消其他子程序的执行,但是如果异常不是CancellationException
,它会将异常传播到系统中,如果您没有捕捉到它,应用程序就会崩溃。管理异常的另一种方法是将必须管理子级启动的异常的CoroutineExceptionHandler
传递到作用域。
https://stackoverflow.com/questions/60073855
复制相似问题