首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如果lifecycleScope是主管,为什么它的子协同系统的失败会导致应用程序崩溃?

如果lifecycleScope是主管,为什么它的子协同系统的失败会导致应用程序崩溃?
EN

Stack Overflow用户
提问于 2020-02-05 10:34:12
回答 3查看 2.9K关注 0票数 9

我是科特林合作公司的新手,我试着理解监管。正如医生们所说:

子女的失败或取消不会导致主管职务失败,也不会影响其其他子女。

好的,我为JVM编写了以下代码:

代码语言:javascript
运行
复制
@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。这就是监督的目的。输出与文档一致:

代码语言:javascript
运行
复制
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编写了几乎相同的代码:

代码语言:javascript
运行
复制
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 由于应用程序崩溃而没有完成它的工作。

代码语言:javascript
运行
复制
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.coroutineContextSupervisorJob() + Dispatchers.Main.immediate,但我在这里看到,子协同的失败影响了父母和其他孩子。

那么监督lifecycleScope的目的是什么呢?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2020-02-05 14:36:48

在您的用例中,很少有东西起着重要的作用。

这里一切都很好,Coroutine #1的失败不影响父母,也不影响Coroutine #2。这就是监督的目的。

  1. CoroutineExceptionHandlerThread.uncaughtExceptionHandler

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,并且由于作业尚未完成(其他协同工作仍在进行中),那么它将打印错误并继续执行其他作业。

代码语言:javascript
运行
复制
supervisorScope.coroutineContext[Job]!!.children.forEach { it.join() }

遵循与异常传播相同的行为定义,其中GlobalScope没有关联的Job对象。

在安卓系统中,Thread.uncaughtExceptionHandler是默认的处理程序,它会在异常情况下关闭应用程序,并显示崩溃对话框。

这就是在不同的生态系统中处理带有或不使用join的异常之间的区别,因此在使用join的kotlin测试中没有终止行为(这不是安卓应用程序中的)。

虽然lifecycleScope.coroutineContext是SupervisorJob() + Dispatchers.Main.immediate,但是在这里我看到了子协同机制的失败影响了父级和其他子级。

  1. 不,由于根本没有子元素,所以子节点没有影响父协同线。您的两个协同器都将与单个父协同器在同一个线程上执行,并且不存在父-子关系(在您的协同器中使用Thread.currentThread()?.name查看线程名),因此在出现异常时,父方将将异常委托给安卓的uncaughtExceptionHandler,这将杀死应用程序(参见第1点)。

所以,您可以使用withContext

代码语言:javascript
运行
复制
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()
            }
        }

或者,为了建立父-子关系,使用相同的作用域调用子协同。

代码语言:javascript
运行
复制
   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块中的异常,并且不会影响其他子协同执行

与儿童合作的结果:

代码语言:javascript
运行
复制
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来进一步简化示例

代码语言:javascript
运行
复制
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")
}

为了避免应用程序崩溃,您可以实现自己的处理程序,以避免应用程序崩溃(除非您真的需要,否则不要这样做,因为这是错误的做法,导致了技术债务)。

需要处理未处理的异常并发送日志文件

票数 4
EN

Stack Overflow用户

发布于 2020-02-05 17:17:07

如果你仔细看看你的输出:

代码语言:javascript
运行
复制
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使用了不同的异常处理程序,它会立即杀死整个应用程序。有关协同作用域的任何内容都不会改变这种行为。

下面是一些代码,您可以尝试查看该机制的运行情况:

代码语言:javascript
运行
复制
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协同工作中改进的领域。

票数 5
EN

Stack Overflow用户

发布于 2020-02-05 13:38:11

问题是,SupervisorJob不像您所期望的那样工作。SupervisorScope的思想是,当一个异常由它的一个子程序启动时,它不会取消其他子程序的执行,但是如果异常不是CancellationException,它会将异常传播到系统中,如果您没有捕捉到它,应用程序就会崩溃。管理异常的另一种方法是将必须管理子级启动的异常的CoroutineExceptionHandler传递到作用域。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60073855

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档