← Назад к вопросам

Как отменить выполнение корутины

2.2 Middle🔥 221 комментариев
#Kotlin основы#Многопоточность и асинхронность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Отмена выполнения корутины

Отмена (cancellation) - это встроенный механизм Kotlin корутин для контролируемого завершения.

Основные способы отмены

1. Через Job.cancel()

val job = launch {
    try {
        delay(5000)
        println("Done")
    } catch (e: CancellationException) {
        println("Cancelled")
        throw e  // Переброс обязателен!
    }
}

// Отменить корутину
job.cancel()

2. Через cancelAndJoin()

val job = launch {
    repeat(10) { i ->
        println("Working $i")
        delay(1000)
    }
}

// Отменить и ждать завершения
job.cancelAndJoin()

3. Через CoroutineScope.cancel()

val scope = CoroutineScope(Dispatchers.Main + Job())

scope.launch {
    delay(5000)
    println("Done")
}

// Отменить ВСЕ корутины в scope
scope.cancel()

4. ViewModel scope (автоматическая отмена)

class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            val data = fetchData()  // Отменится при очистке ViewModel
            _uiState.value = data
        }
    }
    
    // При onCleared() автоматически отменяются все корутины
}

5. Lifecycle scope (автоматическая отмена)

class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        lifecycleScope.launch {
            // Отменится при DESTROY activity
            val data = fetchData()
            updateUI(data)
        }
        
        lifecycleScope.launchWhenStarted {
            // Приостановится при PAUSE, возобновится при RESUME
            observeData()
        }
    }
}

6. Timeout отмена

launchIO {
    try {
        withTimeoutOrNull(5000L) {
            heavyComputation()  // Отменится через 5 секунд
        }
    } catch (e: TimeoutCancellationException) {
        println("Timed out")
    }
}

Внутренний механизм отмены

Как работает cancel():

  1. Устанавливается флаг отмены в Job
  2. При следующей suspend точке (delay, network call и т.д.) выбрасывается CancellationException
  3. Корутина завершается
  4. Finally блоки выполняются
val job = launch {
    try {
        println("1. Start")
        delay(1000)  // Здесь выбросится CancellationException
        println("2. Never reached")
    } finally {
        println("3. Cleanup (выполнится!)")
    }
}

delay(100)
job.cancel()  // Отменить

// Output:
// 1. Start
// 3. Cleanup

Проверка отмены в цикле

launch {
    repeat(1000) { i ->
        println("Item $i")
        delay(100)
        // Или явная проверка
        ensureActive()  // Выбросит CancellationException если отменена
    }
}

// Альтернатива
launch {
    repeat(1000) { i ->
        if (!isActive) return@launch  // Проверить статус
        println("Item $i")
        delay(100)
    }
}

Graceful shutdown

class DataRepository {
    private val scope = CoroutineScope(Dispatchers.IO + Job())
    
    fun loadData() {
        scope.launch {
            try {
                while (isActive) {
                    val data = fetchData()
                    updateCache(data)
                    delay(5000)
                }
            } finally {
                // Очистить ресурсы
                closeConnections()
                saveCacheToDb()
            }
        }
    }
    
    fun cleanup() {
        scope.cancel()  // Отменить все корутины
    }
}

Обработка CancellationException

launch {
    try {
        delay(5000)
    } catch (e: CancellationException) {
        // Обработать отмену
        println("Cancelled")
        throw e  // ВАЖНО: переброс!
    } catch (e: Exception) {
        // Обработать другие ошибки
        println("Error: $e")
    }
}

Отмена с причиной

val job = launch {
    delay(5000)
}

job.cancel(CancellationException("User cancelled"))

// Или с явным сообщением
job.cancel("Timeout")

Проверка состояния Job

val job = launch {
    delay(5000)
}

println("Active: ${job.isActive}")      // true
job.cancel()
println("Cancelled: ${job.isCancelled}") // true
println("Active: ${job.isActive}")       // false

// Ждать завершения
job.join()
println("Completed: ${job.isCompleted}") // true

Супервизор (обработка ошибок в корутинах)

// SupervisorJob не отменяет другие корутины при ошибке
val supervisor = SupervisorJob()
val scope = CoroutineScope(Dispatchers.Main + supervisor)

scope.launch {
    // Ошибка здесь НЕ повлияет на другие корутины
    throw Exception("Error in task 1")
}

scope.launch {
    // Продолжит работать
    delay(5000)
    println("Task 2 completed")
}

Лучшие практики

✅ Делай так:

  • Используй viewModelScope для Activity/Fragment
  • Используй lifecycleScope для привязки к lifecycle
  • Всегда переброс CancellationException в catch
  • Проверяй isActive в циклах
  • Используй withTimeout для длительных операций

❌ Избегай:

  • Игнорирования CancellationException
  • Ловли CancellationException без переброса
  • Забывания про finally блоки
  • Создания неуправляемых CoroutineScope
  • Игнорирования отмены в loops

Вывод

Отмена корутин - это встроенный механизм, поддерживаемый на уровне suspend функций. Используй Job.cancel() или lifecycle-based scope для автоматической отмены при очистке.

Как отменить выполнение корутины | PrepBro