Поймать исключение в Kotlin asyn c сопрограмм и остановить распространение - PullRequest
1 голос
/ 19 апреля 2020

Я хочу поймать исключение, которое генерируется из асин c сопрограмм. Следующий код демонстрирует проблему:

import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    try {
        println(failedConcurrentSum())
    } catch (e: ArithmeticException) {
        println("Computation failed with ArithmeticException")
    }
}

suspend fun failedConcurrentSum() = coroutineScope {
    try {
        val one = async {
            try {
                delay(1000L)
                42
            } finally {
                println("First child was cancelled")
            }
        }

        val two = async<Int> {
            println("Second child throws an exception")
            throw ArithmeticException()
        }

        one.await() + two.await()
    } catch (e: ArithmeticException) {
        println("Using a default value...")
        0
    }
}

Это печатает:

Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException

try-catch внутри failedConcurrentSum не обрабатывает исключение, выданное val two. Я могу убедить себя, что это происходит из-за "структурированного параллелизма".

Однако это не объясняет, почему перенос async внутри coroutineScope ловит исключение:

suspend fun failedConcurrentSum() = coroutineScope {
    try {
        val one = coroutineScope {
            async {
                try {
                    delay(1000L)
                    42
                } finally {
                    println("First child was cancelled")
                }
            }
        }

        val two = coroutineScope {
            async<Int> {
                println("Second child throws an exception")
                throw ArithmeticException()
            }
        }

        one.await() + two.await()
    } catch (e: ArithmeticException) {
        println("Using a default value...")
        0
    }
}

Это печатает:

First child was cancelled
Second child throws an exception
Using a default value...
0

Почему последний ловит исключение, а первый нет?

Ответы [ 2 ]

2 голосов
/ 20 апреля 2020

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

async, сразу же после запуска сопрограммы, поэтому у вас есть две одновременные подпрограммы: одна выполняет код async а другой вызывает соответствующий await. Так как async one также является дочерним по отношению к вызывающему await, его сбой отменяет родителя до завершения вызова await родителя.

Try-catch внутри failedConcurrentSum не обрабатывает исключение, выданное val two.

Это действительно так, если он получает шанс. Но поскольку блок try-catch находится в сопрограмме, которая выполняется одновременно с блоком, завершающим val two Deferred, он просто не получает возможности сделать это до того, как будет отменен из-за сбоя дочерней процедуры.

1 голос
/ 19 апреля 2020

coroutineScope использует Job

По умолчанию сбой любого из дочерних элементов задания приводит к немедленному отказу его родителя и отмене остальных его дочерних элементов. Работа

Вы можете использовать supervisorScope вместо coroutineScope

Сбой или отмена ребенка не приводит к тому, что работа супервизора терпеть неудачу и не влияет на других его детей. SupervisorJob

, но вам нужно дождаться завершения первого async блока.

Используйте coroutineScope внутри try catch, чтобы вернуть значение по умолчанию значение сразу при возникновении исключения

suspend fun failedConcurrentSum() = try {
    coroutineScope {
        val one = async {
            try {
                delay(1000L)
                42
            } finally {
                println("First child was cancelled")
            }
        }

        val two = async<Int> {
            println("Second child throws an exception")
            throw ArithmeticException()
        }

        one.await() + two.await()
    }
} catch (e: ArithmeticException) {
    println("Using a default value...")
    0
}
...