#kotlin #kotlin-coroutines
#kotlin #kotlin-сопрограммы
Вопрос:
У меня есть Generator
класс, который, по сути, генерирует некоторые данные, например:
interface Generator {
suspend fun generate(): String?
}
Существует несколько реализаций. Некоторые из них могут вызывать исключения, а некоторым из них может потребоваться слишком много времени для генерации данных:
class Faulty : Generator {
override suspend fun generate(): String? {
println("Faulty")
throw IllegalArgumentException();
}
}
class Lingering : Generator {
override suspend fun generate(): String? {
println("Lingering")
delay(Duration.ofHours(1))
return null
}
}
Но некоторые реализации достойны
class Good : Generator {
override suspend fun generate(): String {
println("Good")
return "Goooood"
}
}
Что мне нужно сделать, так это собрать данные, сгенерированные списком предварительно настроенных генераторов, предоставив каждому из них время ожидания для its generate
и игнорируя исключения (но регистрируя их):
fun main() = runBlocking {
val generators = listOf(Faulty(), Lingering(), Good())
val results = supervisorScope {
generators
.map { generator ->
async(CoroutineExceptionHandler { context, exception ->
println(exception)
}) {
withTimeoutOrNull(5000) {
generator.generate()
}
}
}
.awaitAll()
.filterNotNull()
}
println(results)
}
Проблема в том, что этот код завершается ошибкой с исключением:
Faulty
Lingering
Good
Exception in thread "main" java.lang.IllegalArgumentException
at Faulty.generate (File.kt:12)
at FileKt$main$1$results$1$1$2$1.invokeSuspend (File.kt:41)
at FileKt$main$1$results$1$1$2$1.invoke (File.kt:-1)
Почему это не supervisorScope
улавливается? Что я делаю не так?
Ответ №1:
Из документации CoroutineExceptionHandler:
Необязательный элемент в контексте сопрограммы для обработки неперехваченных исключений.
и
Сопрограмма, созданная с использованием
async
, всегда улавливает все свои исключения и представляет их в результирующем отложенном объекте, поэтому она не может привести к неперехваченным исключениям.
из этого следует, что ваша async
работа не выдает неперехваченное исключение. Исключение повторно заполняется awaitAll()
вызовом, который выполняется позже. Вы поместили обработчик неперехваченных исключений только в свой async
контекст, поэтому он не будет использоваться.
Кроме того, дочерние сопрограммы в любом случае не генерируют неперехваченные исключения. Их исключения делегируются их корневому предку.
Как объяснялось здесь в последнем разделе, озаглавленном Исключения в контролируемых сопрограммах, дочерние элементы области супервизора должны иметь корневую сопрограмму, использующую обработчик.
Что вы можете сделать, так это обернуть всю задачу в launch
блок, который использует обработчик. По какой-то причине установка обработчика не работает runBlocking
. Может быть, это не считается заданием root?
fun main() = runBlocking{
val job = GlobalScope.launch(CoroutineExceptionHandler { context, exception ->
println(exception)
}) {
val generators = listOf(Faulty(), Lingering(), Good())
val results =
supervisorScope {
generators
.map { generator ->
async {
withTimeoutOrNull(5000) {
generator.generate()
}
}
}
.awaitAll()
.filterNotNull()
}
println(results)
}
job.join()
}
Но я думаю, что, возможно, единственной причиной, по которой вы ввели CoroutineExceptionHandler, было игнорирование исключений. Эта стратегия не сработает, потому что обработчик имеет дело только с неперехваченными исключениями, а это означает, что восстанавливать слишком поздно. На этом этапе задание уже завершилось с ошибкой. Вам нужно будет обернуть ваш generate()
вызов внутри async
блока в try / catch или runCatching
.
Комментарии:
1. Спасибо за ответ! На самом деле, я начал с
generate()
обернутого сtry
помощью /catch
, и это сработало нормально, но потом я подумал об использованииsupervisorScope
, потому что я надеялся, что это позволит просто опуститьtry
… И, да, проблема, похоже, в томawaitAll
, кто выдает, а не вasync