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

Последнее обновление: 21.06.2021

При работе приложения может сложиться необходимость отменить выполнение корутины. Например, в мобильном приложении запущена корутина для загрузки данных с некоторого интернет-ресуса, но пользователь решил перейти к другой странице приложения, и ему больше не нужны эти данные. В этом случае чтобы зря не тратить ресурсу системы, мы можем предусмотреть отмену выполнения корутины.

Для отмены выполнения корутины у объекта Job может применяться метод cancel():

import kotlinx.coroutines.*

suspend fun main() = coroutineScope{

    val downloader: Job = launch{
        println("Начинаем загрузку файлов")
        for(i in 1..5){
            println("Загружен файл $i")
            delay(500L)
        }
    }
    delay(800L)		// установим задержку, чтобы несколько файлов загрузились
    println("Надоело ждать, пока все файлы загрузятся. Прерву-ка я загрузку...")
    downloader.cancel()    // отменяем корутину
    downloader.join()      // ожидаем завершения корутины
    println("Работа программы завершена")
}

В данном случае определена корутина, которая имитирует загрузку файлов. В цикле пробегаемся от 1 до 5 и условно загружаем пять файлов.

Далее вызов метода downloader.cancel() сигнализирует корутине, что надо прервать выполнение. Затем с помощью метода join() ожидаем завершения корутина, которая прервана. В итоге получим консольный вывод наподобие следующего:

Начинаем загрузку файлов
Загружен файл 1
Загружен файл 2
Надоело ждать, пока все файлы загрузятся. Прерву-ка я загрузку...
Работа программы завершена

Также вместо двух методов cancel() и join() можно использовать один сборный метод cancelAndJoin():

import kotlinx.coroutines.*

suspend fun main() = coroutineScope{

    val downloader: Job = launch{
        println("Начинаем загрузку файлов")
        for(i in 1..5){
            println("Загружен файл $i")
            delay(500L)
        }
    }
    delay(800L)
    println("Надоело ждать, пока все файлы загрузятся. Прерву-ка я загрузку...")
    downloader.cancelAndJoin()    // отменяем корутину и ожидаем ее завершения
    println("Работа программы завершена")
}

Обработка исключения CancellationException

Все suspend-функции в пакете kotlinx.coroutines являются прерываемыми (cancellable). Это значит, что они проверяют, прервана ли корутина. И если ее выполнение прервано, они генерируют исключение типа CancellationException. И в самой корутине мы можем перехватить это исключение, чтобы обработать отмену корутины. Например:

import kotlinx.coroutines.*

suspend fun main() = coroutineScope{

    val downloader: Job = launch{
        try {
            println("Начинаем загрузку файлов")
            for(i in 1..5){
                println("Загружен файл $i")
                delay(500L)
            }
        }
        catch (e: CancellationException ){
            println("Загрузка файлов прервана")
        }
        finally{
            println("Загрузка завершена")
        }
    }
    delay(800L)
    println("Надоело ждать. Прерву-ка я загрузку...")
    downloader.cancelAndJoin()    // отменяем корутину и ожидаем ее завершения
    println("Работа программы завершена")
}

Здесь код выполнения корутины обернут в конструкцию try. Если корутина будет прервана извне, то с помощью блока catch и перехвата исключения CancellationException мы сможем обработать отмену корутины.

И если нам надо выполнить некоторые завершающие действия, например, освободить используемые в корутине ресурсы - закрыть файлы, различные подключения к внешним ресурсам, то это можно сделать в блоке finally. Но в данном случае в этом блоке просто выводим диагностическое сообщение.

В итоге при вызове метода downloader.cancel() производейт отмена корутины. Будет сгенерировано исключение, и в корутине в блоке catch мы сможем ее обработать. В итоге получим следующий консольный вывод:

Начинаем загрузку файлов
Загружен файл 1
Загружен файл 2
Надоело ждать. Прерву-ка я загрузку...
Загрузка файлов прервана
Загрузка завершена
Работа программы завершена

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

Подобным образом можно отменять выполнение и корутин, создаваемых с помощью функции async(). В этом случае обычно вызов метода await() помещается в блок try:

import kotlinx.coroutines.*

suspend fun main() = coroutineScope{

    // создаем и запускаем корутину
    val message = async {
        getMessage()
    }
    // отмена корутины
    message.cancelAndJoin()
    
    try {
        // ожидаем получение результата
        println("message: ${message.await()}")
    }
    catch (e:CancellationException){
        println("Coroutine has been canceled")
    }
    println("Program has finished")
}

suspend fun getMessage() : String{
    delay(500L)
    return "Hello"
}

Консольный вывод программы:

Coroutine has been canceled
Program has finished
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850