Пытаюсь лучше понять работу 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()
}
Код будет состоять из двух частей:
println("Start") и delay(1000) (весь код до первой suspend функции и сама функция);
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")
}
Код будет состоять из трёх частей:
fetchData() (весь код до первой suspend функции и сама функция);
processData(data) (весь код после первой suspend функции и по вторую suspend функцию включительно);
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.