Пытаюсь лучше понять работу Kotlin Coroutine. Беру небольшую тему про Kotlin Coroutine и пытаюсь разобраться и написать шпаргалку (максимально кратко и лаконично). Сегодня сильно упрощенно о том, как выглядит скомпилированный класс корутины.

При создании и запуске корутины компилятор создаст специальный объект, который реализует интерфейс Continuation (другими словами, возможно будет понятнее: объект представляет собой реализацию лямбды, переданной в launch и у этого объекта будет своё соcтояние, которое будет меняться по мере выполнения кода, находящегося в этой лямбде). Этот класс содержит всю логику выполнения корутины, включая её состояние и локальные переменные.

Также будут созданы объекты для каждой suspend функции, но в этот раз их не рассматриваем.

Жизненный цикл корутин

Корутина проходит через несколько состояний в течение своего жизненного цикла:

1. Создание: Корутина создается с помощью строителей, таких как launch или async.

2. Запуск: Корутина начинает выполнение, когда её запускают с помощью start() или автоматически, если используется launch.

3. Приостановка: Корутина может быть приостановлена при вызове suspend-функции, например, delay() или await().

4. Возобновление: После завершения асинхронной операции корутина возобновляет выполнение с места приостановки.

5. Завершение: Корутина завершает выполнение, когда весь код внутри неё выполнен или когда она отменена.

Что такое Continuation

Continuation — это объект, который представляет собой состояние выполнения корутины и позволяет возобновить её выполнение после приостановки (это как бы callback, который вызывается, когда корутина готова продолжить выполнение после приостановки).

Continuation — это механизм, который позволяет Kotlin Coroutines приостанавливать и возобновлять выполнение. Он является ключевым элементом реализации suspend-функций и асинхронного программирования в Kotlin.

Когда корутина приостанавливается (например, при вызове suspend-функции), её состояние сохраняется в объекте Continuation. Этот объект содержит информацию о том, где именно корутина была приостановлена, и как её можно продолжить после завершения асинхронной операции.

В Kotlin Continuation — это интерфейс, который выглядит следующим образом:

interface Continuation<in T> {
    val context: CoroutineContext
    fun resumeWith(result: Result<T>)
}

Пример.

Для следующего кода:

suspend fun mySuspendFunction() {
    println("Start")
    delay(1000) 
    println("End")
}

   launch {
        mySuspendFunction() 
    }

Код будет состоять из двух частей:

  1. println("Start") и delay(1000) (весь код до первой suspend функции и сама функция);

  2. println("End") (весь оставшийся код, т.к. больше нет suspend функций).

Создастся следующий класс (код компилируется в байт-код, код на java привожу для упрощения):

class GeneratedContinuation extends SuspendLambda {
    int label; // Текущее состояние
  
    @Override
    Object invokeSuspend(Object result) {
        switch (label) {
            case 0:
                println("Start");
                label = 1;
                if (delay(1000, this) == COROUTINE_SUSPENDED) 
                    return COROUTINE_SUSPENDED; // Приостановка
                // После возобновления переходит к case 1
            case 1:
                println("End");
                return Unit.INSTANCE;
        }
        throw IllegalStateException();
    }
}

Метка label - это указатель на текущую точку выполнения внутри корутины, он используется для отслеживания текущего состояния корутины. При каждом вызове suspend-функции значение label изменяется, чтобы указать, с какого места нужно продолжить выполнение после приостановки.

При первом вызове (label = 0) выполняется код до delay().

delay(1000, this) передаёт Continuation (this) в suspend функцию, для того, чтобы после окончания своей работы она вызвала invokeSuspend и работа продолжилась. При приостановке возвращается COROUTINE_SUSPENDED. COROUTINE_SUSPENDED — это специальное значение, которое возвращается, когда корутина приостанавливается. Это является сигналом для механизма корутин: "Выполнение приостановлено, нужно освободить текущий поток".

После истечения времени приостановки, т.е. после завершения suspend функции работа продолжается со следующим значением label. В нашем случае по окончанию delay(1000) выполнение продолжается с label = 1.

Возврат значения suspend функцией

Для возврата значений у нас есть в функции invokeSuspend параметр result.

Рассмотрим пример

launch {
    val data = fetchData() // suspend fun

    processData(data) // suspend fun

    println("Data is processed")
}

Код будет состоять из трёх частей:

  1. fetchData() (весь код до первой suspend функции и сама функция);

  2. processData(data) (весь код после первой suspend функции и по вторую suspend функцию включительно);

  3. println("Data is processed") (весь оставшийся код, т.к. больше нет suspend функций).

class GeneratedContinuation extends SuspendLambda {
    int label; // Текущее состояние
    byte[] data;
  
    @Override
    Object invokeSuspend(Object result) {
        switch (label) {
            case 0:
                label = 1;
                data = fetchData(this);
                return COROUTINE_SUSPENDED; // Приостановка
                // После возобновления переходит к case 1
            case 1:
                data = (byte[]) result;
                label = 2;
                processData(data, this);
                return COROUTINE_SUSPENDED; // Приостановка
                // После возобновления переходит к case 2
            case 2:
               println("Data is processed");
                return Unit.INSTANCE;
        }
        throw IllegalStateException();
    }
}

Т.е. suspend функция fetchData после окончания работы вызовет invokeSuspend на "объекте корутины" и передаст в качестве параметра result результат своей работы - массив байт data.

Ссылка на документацию Kotlin.

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