Как работают корутины
Работа корутин в Kotlin под капотом основана на использовании преобразования кода в функции с продолжениями (continuations). Корутинная модель Kotlin реализуется на основе кооперативной многозадачности, при которой задачи приостанавливаются и возобновляются в зависимости от определённых условий (например, ожидание завершения сетевого запроса). Под капотом компилятор преобразует корутины в структуры с состоянием и кодом продолжения, которые вызываются по мере необходимости.
Suspending Functions (приостанавливающиеся функции):
Continuation
, который хранит текущее состояние выполнения и позволяет возобновить выполнение кода в нужной точке.Coroutine Context (контекст корутины):
Dispatchers.IO
, Dispatchers.Main
), на котором корутина будет выполняться.Coroutine Builder (создание корутин):
launch
, async
, withContext
— это строительные блоки, которые позволяют создавать корутины и управлять их жизненным циклом.import kotlinx.coroutines.*
fun main() = runBlocking {
launch(Dispatchers.IO) {
println("Coroutine started on thread: ${Thread.currentThread().name}")
val result = longRunningTask()
println("Result: $result")
}
}
suspend fun longRunningTask(): Int {
delay(1000)
return 42
}
Здесь функция longRunningTask
является приостанавливающейся функцией, которая использует delay
для паузы выполнения на 1 секунду, а затем возвращает значение 42.
При компиляции этот код преобразуется в серию состояний, которые Kotlin поддерживает с помощью Continuation
. Каждая приостанавливающаяся функция компилируется в обычную функцию, которая принимает объект Continuation
как дополнительный параметр. Вот пример сгенерированного псевдокода (упрощённо) для функции longRunningTask
:
public Object longRunningTask(Continuation<? super Integer> continuation) {
if (continuation.label == 0) {
continuation.label = 1;
delay(1000, continuation);
return COROUTINE_SUSPENDED;
} else if (continuation.label == 1) {
return 42;
}
return null;
}
Объяснение:
label
используется для хранения состояния корутины.delay
), управление возвращается, а выполнение кода продолжается, когда операция завершится.COROUTINE_SUSPENDED
сигнализирует о том, что выполнение приостановлено.Рассмотрим, что происходит с функцией longRunningTask()
:
delay
. Корутина приостанавливается, и текущее состояние сохраняется в объекте Continuation
.Это сделано так, чтобы не блокировать основной поток. Корутина может быть возобновлена позже на любом потоке, что и делает её эффективной для асинхронного программирования.
Continuation
в Kotlinsuspend fun exampleContinuation(): Int = suspendCoroutine { continuation ->
Thread {
Thread.sleep(1000)
continuation.resume(42)
}.start()
}
Здесь мы вручную создаём корутину с использованием suspendCoroutine
. Она приостанавливает выполнение до тех пор, пока поток не завершит работу, а затем продолжает выполнение с результатом.
label
для отслеживания текущего состояния. Каждый раз при возобновлении выполнения корутина проверяет эту метку, чтобы продолжить выполнение с нужного места.Continuation
содержит всю информацию о текущем состоянии выполнения, включая текущий контекст (например, диспетчер), метку состояния и промежуточные результаты.Рассмотрим теперь случай с несколькими приостанавливающими функциями:
suspend fun complexCoroutine(): Int {
println("Starting complexCoroutine")
val first = suspendFunction(5)
val second = suspendFunction(first)
return second * 2
}
Компилированный код для этой функции будет включать несколько состояний:
public final Object complexCoroutine(Continuation<? super Integer> continuation) {
Object result;
switch (continuation.label) {
case 0: {
System.out.println("Starting complexCoroutine");
continuation.label = 1;
result = suspendFunction(5, continuation);
if (result == COROUTINE_SUSPENDED) {
return COROUTINE_SUSPENDED;
}
}
case 1: {
int first = (Integer) result;
continuation.label = 2;
result = suspendFunction(first, continuation);
if (result == COROUTINE_SUSPENDED) {
return COROUTINE_SUSPENDED;
}
}
case 2: {
int second = (Integer) result;
return second * 2;
}
default: {
throw new IllegalStateException("This coroutine has already completed");
}
}
}
Здесь происходит несколько переходов между состояниями:
Таким образом, корутины под капотом работают через автоматическое управление состояниями с помощью объекта Continuation
, а переходы между состояниями управляются внутренними метками (например, label
), что делает возможным приостановку и возобновление выполнения функций.