Безопасная асинхронность в заданной области - PullRequest
3 голосов
/ 04 октября 2019

Я хочу запустить асинхронную сопрограмму с функцией приостановки в данном родительском элементе CoroutineScope, чтобы получить Deferred, который затем может быть использован из любой сопрограммы в этой области.

Мне бы хотелось, чтобы еезадание будет отменено, если задание родителя отменено, но если функция приостановки выдает исключение, мне нужно, чтобы оно было получено в результирующем Deferred без отмены родственных заданий в родительской области.

То, как яэто работает нормально, но мне интересно, есть ли более простой и более сомнительный способ, чем этот:

fun <T> callIt(scope: CoroutineScope, block: suspend () -> T) : Deferred<T> {
    val result = CompletableDeferred<T>()
    scope.launch {
        try {
            result.complete(block())
        } catch (e: Throwable) {
            result.completeExceptionally(e)
        }
    }
    return result
}

Мне нравится, что обработка исключений из приостановившегося block, очевидно, то, что я хочу, ноЯ не очень рад создать async из launch

Вещи, которые не работают:

  • Асинхронное задание с обработчиком исключений. async ловит его исключения, но задание все равно не выполняется и отменяет его родителя. Как прокомментировал @Rene: документация async гласит: «она отменяет родительское задание (или внешнюю область) из-за невозможности применения парадигмы структурированного параллелизма».

1 Ответ

1 голос
/ 04 октября 2019

Вы можете сделать это без CompletableDeferred. Вам необходимо создать SupervisorJob и позволить заданию из CoroutineScope быть родителем нового задания. Если область отменяется, async -программа также отменяется. Но никакое исключение внутри async -программы не отменяет родительскую область.

Из-за открытой проблемы https://github.com/Kotlin/kotlinx.coroutines/issues/1578 нам необходимо явно выполнить SupervisorJob.

fun <T> callIt(scope: CoroutineScope, block: suspend () -> T): Deferred<T> {
    val supervisorJob = SupervisorJob(scope.coroutineContext[Job])
    val deferred = scope.async(supervisorJob) {
        block()
    }
    deferred.invokeOnCompletion {
        supervisorJob.complete()
    }
    return deferred
}
...