CoroutineExceptionHandler: глобальный обработчик неперехваченных исключений
Устанавливайте CoroutineExceptionHandler для записи в журнал или восстановления после необработанных исключений.
«CoroutineExceptionHandler: глобальный обработчик неперехваченных исключений» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.
Что такое CoroutineExceptionHandler?
CoroutineExceptionHandler — это элемент контекста, который обрабатывает необработанные исключения корутин, для которых не задан обработчик исключений. Он служит крайней мерой.
import kotlinx.coroutines.*
val handler = CoroutineExceptionHandler { context, exception ->
println("Caught unhandled: ${exception.message}")
}
fun main() = runBlocking {
val scope = CoroutineScope(Dispatchers.Default + handler)
scope.launch { throw RuntimeException("oops") }
delay(100)
}Только для корневых корутин
CoroutineExceptionHandler перехватывает исключения только корневых корутин, запущенных непосредственно в области видимости. Дочерние корутины передают исключения родительской корутине, а не обработчику.
import kotlinx.coroutines.*
val handler = CoroutineExceptionHandler { _, e -> println("Handler: ${e.message}") }
fun main() = runBlocking {
// Root coroutine — handler fires:
CoroutineScope(handler).launch {
throw RuntimeException("root error")
}
delay(100)
// NOT handler (child of coroutineScope):
// launch { launch { throw RuntimeException() } }
}Не подавляет исключение
Обработчик вызывается после того, как исключение уже отменило корутину. Он нужен для ведения журналов, отправки отчётов о сбоях или очистки ресурсов, но не для возобновления выполнения.
import kotlinx.coroutines.*
val handler = CoroutineExceptionHandler { _, e ->
println("[CrashReport] ${e::class.simpleName}: ${e.message}")
// send to Crashlytics, Sentry, etc.
}
fun main() = runBlocking {
CoroutineScope(SupervisorJob() + handler).apply {
launch { throw IllegalStateException("state error") }
launch { delay(100); println("still alive") }
delay(200)
cancel()
}
}Обработчик с SupervisorJob
Сочетание SupervisorJob + CoroutineExceptionHandler — стандартный шаблон для долгоживущих областей видимости: дочерние корутины завершаются с ошибками независимо друг от друга, а необработанные сбои записываются в журнал.
import kotlinx.coroutines.*
class AppScope {
private val handler = CoroutineExceptionHandler { _, e ->
println("Uncaught: ${e.message}")
}
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default + handler)
}
fun main() = runBlocking {
val app = AppScope()
app.scope.launch { throw RuntimeException("task failed") }
app.scope.launch { delay(100); println("other task ok") }
delay(200)
app.scope.cancel()
}Обработчик для асинхронных вычислений
Для async исключения сохраняются в Deferred и выбрасываются при вызове await(). Обработчик НЕ срабатывает для async, если отложенный результат не ожидается.
import kotlinx.coroutines.*
val handler = CoroutineExceptionHandler { _, e -> println("Handler: ${e.message}") }
fun main() = runBlocking {
val scope = CoroutineScope(SupervisorJob() + handler)
val deferred = scope.async { throw RuntimeException("async error") }
try {
deferred.await() // exception thrown here
} catch (e: RuntimeException) {
println("Caught from await: ${e.message}")
}
delay(50)
scope.cancel()
}Сравнение с UncaughtExceptionHandler потока
В отличие от UncaughtExceptionHandler в Java, обработчик Kotlin является частью контекста корутины и действует только в корутинах соответствующей области видимости.
import kotlinx.coroutines.*
// Java style (applies to threads):
Thread.setDefaultUncaughtExceptionHandler { t, e ->
println("Thread ${t.name} threw: ${e.message}")
}
// Kotlin coroutine style (applies to coroutines in scope):
val handler = CoroutineExceptionHandler { _, e ->
println("Coroutine threw: ${e.message}")
}
fun main() = runBlocking { println("Handlers target different concurrency models") }Шаблон ведения журналов с MDC
В серверных приложениях обработчик может извлечь из контекста корутины такие сведения, как идентификаторы запросов, чтобы вести структурированные журналы перед передачей данных в систему ведения журналов.
import kotlinx.coroutines.*
val handler = CoroutineExceptionHandler { ctx, e ->
val jobName = ctx[CoroutineName]?.name ?: "unknown"
println("[${jobName}] ERROR: ${e.message}")
}
fun main() = runBlocking {
CoroutineScope(SupervisorJob() + handler).launch(CoroutineName("DataLoader")) {
throw RuntimeException("fetch failed")
}
delay(100)
}Интеграция с отчётами о сбоях
Используйте обработчик для передачи необработанных исключений в сервисы отчётов о сбоях, например Firebase Crashlytics или Sentry.
import kotlinx.coroutines.*
object CrashReporter {
fun record(e: Throwable) = println("[Crashlytics] ${e.message}")
}
val handler = CoroutineExceptionHandler { _, e ->
if (e !is CancellationException) CrashReporter.record(e)
}
val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Default + handler)
fun main() = runBlocking {
appScope.launch { throw RuntimeException("unhandled in production") }
delay(100)
appScope.cancel()
}Наследование обработчика
Обработчик из родительского контекста НЕ наследуется дочерними корутинами автоматически. Он должен находиться в контексте корневой корутины, чтобы сработать.
import kotlinx.coroutines.*
val handler = CoroutineExceptionHandler { _, e -> println("Handler: ${e.message}") }
fun main() = runBlocking {
// Handler only fires at root level:
CoroutineScope(SupervisorJob() + handler).launch {
// Child of root — exception propagates to root handler:
launch { throw RuntimeException("nested") }
}
delay(100)
}Итоги рекомендаций
Всегда устанавливайте CoroutineExceptionHandler в областях видимости уровня приложения или отдельной функциональности. Записывайте в журнал каждое исключение, не связанное с отменой. Никогда не полагайтесь на него в управлении потоком выполнения — он предназначен только для наблюдаемости.
import kotlinx.coroutines.*
val globalHandler = CoroutineExceptionHandler { ctx, e ->
if (e !is CancellationException) {
println("[ERROR] ${ctx[CoroutineName]?.name}: ${e.message}")
}
}
val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Default + globalHandler)
fun main() = runBlocking {
appScope.launch(CoroutineName("Auth")) { throw RuntimeException("token expired") }
delay(100); appScope.cancel()
}Быстрая проверка
Для какого типа корутин CoroutineExceptionHandler НЕ срабатывает автоматически?
Повторение
CoroutineExceptionHandler — наблюдатель крайней инстанции за необработанными исключениями в корневых корутинах. Объединяйте его с SupervisorJob в долгоживущих областях видимости. Используйте его для ведения журналов и отправки отчётов о сбоях, но не для управления потоком выполнения.
Часто задаваемые вопросы
Урок «CoroutineExceptionHandler: глобальный обработчик неперехваченных исключений» бесплатный?
Да — полный текст урока «CoroutineExceptionHandler: глобальный обработчик неперехваченных исключений» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.
Чему я научусь в уроке «CoroutineExceptionHandler: глобальный обработчик неперехваченных исключений»?
Устанавливайте CoroutineExceptionHandler для записи в журнал или восстановления после необработанных исключений. Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Kotlin Academy?
Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «CoroutineExceptionHandler: глобальный обработчик неперехваченных исключений»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Kotlin Academy?
Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- SupervisorJob и Job: изоляция сбоев
- CoroutineExceptionHandler: глобальный обработчик неперехваченных исключений
- Распространение исключений в async/await
- Проектирование устойчивых архитектур сопрограмм