kotlin - 我们什么时候使用launch(SupervisorJob())?

标签 kotlin kotlin-coroutines

我看过一些教程,将 SupervisorJob 传递给 CoroutineScope 以避免所有协程作业在其子进程之一失败时被取消。 在run3中,我认为将SupervisorJob传递给launch可以得到相同的结果,但显然事实并非如此。如果出现异常,它似乎允许重用协程(如果从 launch 中删除 SupervisorJob,则第二个 run2 调用将不会运行协程作业),但它的行为不像 supervisorScope,后者的其他子作业可以继续(在示例中是第一个 test1.run 调用)。我想知道在什么场景下我们可以使用这种方式?因为将其传递给 launch 构造函数看起来是合法的。

package coroutine.exceptions

import kotlinx.coroutines.*

fun log(msg: String) = println("$msg (${Thread.currentThread().name})")

val logExceptionHandler = CoroutineExceptionHandler { _, e ->
    log(e.localizedMessage)
}

fun main() = runBlocking {

    TestReuseCoroutineAfterException4("test1").run {
        run1(true)
        delay(100)
        println()

        run1(false)
        delay(100)
    }

    println("================================================================")

    TestReuseCoroutineAfterException4("test2").run {
        run2(true)
        delay(100)
        println()

        run2(false)
        delay(100)
    }

    println("================================================================")

    TestReuseCoroutineAfterException4("test3").run {
        run3(true)
        delay(100)
        println()

        run3(false)
        delay(100)
        println()
    }

    log("finished")
}

class TestReuseCoroutineAfterException4(
    private val testName: String
) : CoroutineScope by CoroutineScope(CoroutineName(testName)) {

    // by passing a Job, we can let the exception propagate to this coroutine scope instead of the
    // root one, which allows us to reuse the root scope.
    fun run1(throwException: Boolean) = launch(logExceptionHandler + Job()) {

        val logPrefix = "$testName.run1:"

        coroutineScope {

            launch {

                launch {
                    if (throwException)
                        throw RuntimeException("$logPrefix throw exception")
                    else
                        log("$logPrefix done (job#1-1)")
                }.join()

                launch {
                    log("$logPrefix done (job#1-2)")
                }.join()


                log("$logPrefix done (job#1)")

            }.join()

            launch {
                log("$logPrefix done (job#2)")
            }.join()
        }
    }

    suspend fun run2(throwException: Boolean) {

        val logPrefix = "$testName.run2:"

        supervisorScope {

            launch(logExceptionHandler) {

                launch {
                    if (throwException)
                        throw Exception("$logPrefix throw exception")
                    else
                        log("$logPrefix done (job#1-1)")
                }.join()

                launch {
                    log("$logPrefix done (job#1-2)")
                }.join()


                log("$logPrefix done (job#1)")

            }.join()

            // this will be run.
            launch {
                log("$logPrefix done (job#2)")
            }.join()
        }

    }

    fun run3(throwException: Boolean) {

        val logPrefix = "$testName.run3:"

        launch(logExceptionHandler + SupervisorJob()) {

            launch {

                launch {
                    if (throwException)
                        throw Exception("$logPrefix throw exception")
                    else
                        log("$logPrefix done (job#1-1)")
                }.join()

                launch {
                    log("$logPrefix done (job#1-2)")
                }.join()


                log("$logPrefix done (job#1)")

            }.join()

            // this will still be run.
            launch {
                log("$logPrefix done (job#2)")
            }.join()
        }

    }

}

输出

test1.run1: throw exception (DefaultDispatcher-worker-2 @test1#2)

test1.run1: done (job#1-1) (DefaultDispatcher-worker-2 @test1#7)
test1.run1: done (job#1-2) (DefaultDispatcher-worker-2 @test1#8)
test1.run1: done (job#1) (DefaultDispatcher-worker-2 @test1#6)
test1.run1: done (job#2) (DefaultDispatcher-worker-2 @test1#9)
================================================================
test2.run2: throw exception (main @coroutine#10)
test2.run2: done (job#2) (main @coroutine#12)

test2.run2: done (job#1-1) (main @coroutine#14)
test2.run2: done (job#1-2) (main @coroutine#15)
test2.run2: done (job#1) (main @coroutine#13)
test2.run2: done (job#2) (main @coroutine#16)
================================================================
test3.run3: throw exception (DefaultDispatcher-worker-2 @test3#18)

test3.run3: done (job#1-1) (DefaultDispatcher-worker-4 @test3#22)
test3.run3: done (job#1-2) (DefaultDispatcher-worker-4 @test3#23)
test3.run3: done (job#1) (DefaultDispatcher-worker-4 @test3#21)
test3.run3: done (job#2) (DefaultDispatcher-worker-4 @test3#24)

finished (main @coroutine#1)

Process finished with exit code 0

最佳答案

if you remove the SupervisorJob from launch, second run2 call won't run the coroutine job

出现此行为的原因不是您正在传递 SupervisorJob,而是您正在向其传递任何 类型的 Job。尝试将 + SupervisorJob() 替换为 + Job(),第二次调用 run2() 将执行协程。

主要区别在于,当您将显式作业传递给 launch 时,它会成为已启动协程的父作业,而不是 TestReuseCoroutineAfterException4 中的主作业。因此,协程失败不会取消主作业,并且效果仅限于单个调用。

不鼓励将作业直接传递给 launch ,因为它会破坏结构化并发并创建您所经历的奇怪语义。

关于kotlin - 我们什么时候使用launch(SupervisorJob())?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60026116/

相关文章:

android - 如何修复 Koin lib 中的 "No compatible definition found for type ' 上下文?

multithreading - Kotlin中,一个线程一次只能运行一个协程?

kotlin - CoroutineExceptionHandler 应该如何处理 OutOfMemoryError 或其他 fatal error ?

performance - Kotlin:coroutineScope 比 GlobalScope 慢

kotlin - JetPack 撰写 : Adding click duration

Kotlin ,IntelliJ : math operator not working

Android资源链接在绑定(bind)适配器数据绑定(bind)中失败

android - Kotlin 多平台项目

spring-mvc - 除非使用GobalScope调用,否则将依次调用kotlin异步调用

android - Kotlin协程:为什么直到最后我才能看到日志?