Почему этот CoroutineExceptionHandler ни разу не вызывается при сбое?
Обработчик подключён, чтобы упавшая загрузка логировалась, а не роняла приложение. Корутина действительно падает, приложение падает вместе с ней, а обработчик так и не вызывается.
Ограничения: сбой обязан дойти до обработчика, владельцем работы должен остаться viewModelScope, а сам обработчик нельзя дублировать на каждом вызове.
private val handler = CoroutineExceptionHandler { _, e -> Log.e("vm", "load failed", e) }
fun load() = viewModelScope.launch {
launch(handler) {
throw IOException("boom")
}
}
Определите причину и исправьте её.
Обработчик срабатывает только на корутине, которая обрабатывает сбой, — на корне scope. На ребёнке он игнорируется: исключение уходит наверх, родителю. Ставьте его в контекст scope. async его не зовёт вовсе — исключение перебрасывает await().
- ✗Ставить обработчик на ребёнка вместо корня scope
- ✗Ждать срабатывания обработчика на сбое внутри
async - ✗Думать, что
try/catchвокругlaunchпоймает брошенное ребёнком
- →Что меняет
SupervisorJobв том, какая корутина обрабатывает сбой? - →Куда уходит исключение, если обработчик не установлен вовсе?
Причина
CoroutineExceptionHandler — не «try/catch на корутину». Он вызывается только той корутиной, которая действительно обрабатывает сбой, а это корень scope. Ребёнок ничего не обрабатывает: он передаёт исключение родителю, поэтому обработчик в его контексте просто игнорируется.
fun load() = viewModelScope.launch {
launch(handler) { // обработчик на РЕБЁНКЕ — игнорируется
throw IOException("boom")
}
}
Исправление
Обработчик должен лежать в контексте scope (или корневого launch):
private val vmScope = viewModelScope + CoroutineExceptionHandler { _, e ->
Log.e("vm", "load failed", e)
}
fun load() = vmScope.launch { // корень scope — сюда и придёт сбой ребёнка
launch { throw IOException("boom") }
}
Два следствия, о которых спрашивают дальше: под SupervisorJob сбой обрабатывает сама упавшая корутина, поэтому там обработчик на «ребёнке» уже сработает; а async не зовёт обработчик никогда — его исключение хранится в Deferred и перебрасывается из await().