Kotlin Academy · Урок

Очистка с finally и NonCancellable

Освобождайте ресурсы в отменённых сопрограммах с помощью finally и withContext(NonCancellable).

Урок 3 из 413 шагов

«Очистка с finally и NonCancellable» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.

Отмена и завершающие блоки

При отмене корутины точки приостановки выбрасывают CancellationException. Код в блоках finally выполняется всегда, однако сам finally выполняется в уже отменённом контексте корутины.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try {
            delay(1000)
        } finally {
            println("finally: cleaning up")
        }
    }
    delay(50)
    job.cancel()
    job.join()
}

Проблема: приостановка в завершающем блоке

Если попытаться вызвать delay или выполнить другую приостановку внутри finally отменённой корутины, функция немедленно выбросит исключение, поскольку контекст уже отменён.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try {
            delay(1000)
        } finally {
            // delay(100) // throws CancellationException here!
            println("Sync cleanup OK; async cleanup needs NonCancellable")
        }
    }
    delay(50); job.cancel(); job.join()
}

withContext(NonCancellable)

Оборачивайте вызовы приостановки в finally с помощью withContext(NonCancellable), чтобы выполнять их в контексте, который нельзя отменить, даже после отмены родительской корутины.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try {
            delay(1000)
        } finally {
            withContext(NonCancellable) {
                delay(100)  // OK: runs despite cancellation
                println("Async cleanup complete")
            }
        }
    }
    delay(50); job.cancel(); job.join()
}

Когда использовать NonCancellable

Используйте NonCancellable для закрытия подключений к базе данных, записи журналов, отправки событий аналитики и любой асинхронной очистки, которая должна завершиться независимо от отмены.

import kotlinx.coroutines.*
suspend fun closeConnection() {
    println("Closing DB connection...")
    delay(50) // simulate async close
    println("Connection closed")
}
fun main() = runBlocking {
    val job = launch {
        try { delay(1000) }
        finally { withContext(NonCancellable) { closeConnection() } }
    }
    delay(50); job.cancel(); job.join()
}

NonCancellable — это не область действия

NonCancellable — это Job, который можно передать в withContext. Не используйте его напрямую как область действия корутины — применяйте его только для очистки в finally.

import kotlinx.coroutines.*
fun main() = runBlocking {
    // WRONG usage:
    // launch(NonCancellable) { ... } // launches a coroutine that cannot be cancelled
    // RIGHT usage:
    launch {
        try { delay(500) }
        finally {
            withContext(NonCancellable) {
                println("Safe cleanup")
            }
        }
    }.also { delay(50); it.cancel(); it.join() }
}

Очистка нескольких ресурсов

Объединяйте несколько операций очистки внутри одного блока withContext(NonCancellable).

import kotlinx.coroutines.*
fun main() = runBlocking {
    launch {
        try {
            delay(1000)
        } finally {
            withContext(NonCancellable) {
                delay(20); println("Step 1: flush cache")
                delay(20); println("Step 2: close socket")
                delay(20); println("Step 3: log audit")
            }
        }
    }.also { delay(50); it.cancel(); it.join() }
}

Шаблон обработки ошибок и очистки

Сочетайте обработку бизнес-ошибок через try-catch с очисткой в finally — это стандартный структурированный шаблон.

import kotlinx.coroutines.*
fun main() = runBlocking {
    launch {
        try {
            delay(100)
            throw RuntimeException("business error")
        } catch (e: RuntimeException) {
            println("Handling: ${e.message}")
        } finally {
            withContext(NonCancellable) {
                println("Always cleanup")
            }
        }
    }.join()
}

Порядок очистки

Блоки finally выполняются в обратном порядке вложенности. Внешний блок try-finally выполняется после внутренних, как и в обычном коде Java/Kotlin.

import kotlinx.coroutines.*
fun main() = runBlocking {
    launch {
        try {
            try {
                delay(1000)
            } finally { println("Inner cleanup") }
        } finally { println("Outer cleanup") }
    }.also { delay(50); it.cancel(); it.join() }
}

Автоматическое закрытие ресурсов AutoCloseable

Для ресурсов AutoCloseable используйте расширение Kotlin use { }: оно автоматически вызывает close(), в том числе при исключениях или отмене.

import kotlinx.coroutines.*
class Connection : AutoCloseable {
    init { println("Opened") }
    override fun close() { println("Closed") }
    suspend fun query(): String { delay(50); return "result" }
}
fun main() = runBlocking {
    val result = Connection().use { it.query() }
    println(result)
}

Запись причины отмены в журнал

Получайте сообщение об отмене из CancellationException в блоках обработки или завершения, чтобы записать причину остановки корутины в журнал.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try {
            delay(1000)
        } catch (e: CancellationException) {
            println("Cancelled: ${e.message}")
            throw e
        }
    }
    delay(50)
    job.cancel("user navigated away")
    job.join()
}

Очистка CoroutineScope с помощью invokeOnCompletion

Job.invokeOnCompletion регистрирует обратный вызов, который выполняется после завершения задания по любой причине, — это альтернатива finally для очистки на уровне родительской корутины.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        delay(1000)
    }
    job.invokeOnCompletion { cause ->
        if (cause != null) println("Ended with: ${cause::class.simpleName}")
        else println("Completed normally")
    }
    delay(50)
    job.cancel()
    job.join()
}

Быстрая проверка

Для чего нужен withContext(NonCancellable) в завершающем блоке finally?

Итоги

finally всегда выполняется при отмене. Оборачивайте вызовы приостановки внутри finally в withContext(NonCancellable), чтобы они завершались несмотря на отмену. Для ресурсов AutoCloseable используйте use { }.

Можно начать бесплатно

Изучай Kotlin с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
51
Уроки
203

Часто задаваемые вопросы

Урок «Очистка с finally и NonCancellable» бесплатный?

Да — полный текст урока «Очистка с finally и NonCancellable» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.

Чему я научусь в уроке «Очистка с finally и NonCancellable»?

Освобождайте ресурсы в отменённых сопрограммах с помощью finally и withContext(NonCancellable). Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Kotlin Academy?

Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Очистка с finally и NonCancellable»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Kotlin Academy?

Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Кооперативная отмена: isActive и ensureActive
  2. withTimeout и withTimeoutOrNull
  3. Очистка с finally и NonCancellable
  4. Распространение отмены в иерархиях сопрограмм
← Назад к Kotlin Academy