Когда мы используем запуск (SupervisorJob ())? - PullRequest
2 голосов
/ 02 февраля 2020

Я видел обучающие программы, передающие SupervisorJob в CoroutineScope, чтобы избежать отмены всех заданий сопрограммы в случае сбоя одного из его дочерних элементов. В run3 я думал, что передача SupervisorJob в launch может дать тот же результат, но, очевидно, это не так. Похоже, что Coroutine можно использовать повторно, если есть исключение (если вы удалите SupervisorJob из launch, второй вызов run2 не запустит задание сопрограммы), но он не будет вести себя как supervisorScope , чья работа с другими детьми может продолжаться (в примере первый test1.run вызов). Интересно, по какому сценарию мы можем использовать этот способ? Потому что выглядит, как le git передать его конструктору 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()
        }

    }

}

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 Ответ

3 голосов
/ 02 февраля 2020

если вы удалите SupervisorJob из launch, второй вызов run2 не запустит задание сопрограммы

Причина такого поведения не в том, что вы передаете SupervisorJob, но вы передаете ему любой вид Job. Попробуйте заменить + SupervisorJob() на + Job(), и второй вызов run2() выполнит сопрограммы.

Основное отличие состоит в том, что когда вы передаете явное задание в launch, оно становится родительским заданием. запущенной сопрограммы вместо основной работы в TestReuseCoroutineAfterException4. Следовательно, ошибка сопрограммы не отменяет мастер-задание, а эффекты локализуются в одном вызове.

Передача задания непосредственно в launch не рекомендуется, поскольку нарушает структурированный параллелизм и создает странную семантику, которая Вы испытали.

...