When do we use launch(SupervisorJob())?

荒凉一梦 提交于 2021-01-27 20:10:20

问题


I have seen tutorials passing SupervisorJobto CoroutineScope to avoid all coroutine jobs being cancelled if one of its child fails. In run3, I thought passing SupervisorJob to launch can get the same result, but apparently it does not. It seems to allow the Coroutine to be reused if there is an exception (if you remove the SupervisorJob from launch, second run2 call won't run the coroutine job), but it dose not behave like supervisorScope, whose other children job can continue (in the example the first test1.run call) . I wonder in what scenario we can use this way? Because it looks legit to pass it to launch constructor.

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()
        }

    }

}

output

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

回答1:


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

The reason for this behavior is not that you are passing a SupervisorJob, but that you are passing any kind of Job to it. Try replacing + SupervisorJob() with + Job() and the second invocation of run2() will execute the coroutines.

The main difference is that, when you pass an explicit job to launch, it becomes the parent job of the launched coroutine instead of the master job within TestReuseCoroutineAfterException4. Coroutine failure therefore doesn't cancel the master job and the effects are localized to a single invocation.

Passing a job directly to launch is not an encouraged practice because it breaks structured concurrency and creates the weird semantics that you experienced.



来源:https://stackoverflow.com/questions/60026116/when-do-we-use-launchsupervisorjob

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!