Как корутины устроены под капотом
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как устроены корутины под капотом
Это глубокий вопрос о внутреннем механизме Kotlin корутин. Корутины — это не потоки, а совсем другой механизм выполнения кода.
Основная идея: State Machine
Каждая корутина компилятор преобразует в State Machine (автомат с состояниями). Это ключ к пониманию.
// Исходный код (Kotlin)
suspend fun loadData(): String {
val data = fetchFromNetwork() // suspend point 1
val processed = process(data) // suspend point 2
return processed
}
// Компилятор создает:
class LoadDataContinuation(val completion: Continuation<String>) : Continuation<String> {
var state = 0
var result: String? = null
override fun resumeWith(result: Result<String>) {
when (state) {
0 -> {
state = 1
fetchFromNetwork().invokeOnCompletion { data ->
this.result = data
resumeWith(Result.success(data))
}
}
1 -> {
state = 2
val processed = process(this.result!!)
resumeWith(Result.success(processed))
}
2 -> {
completion.resumeWith(Result.success(this.result!!))
}
}
}
}
Continuation: Основной интерфейс
Continuation — это объект, который содержит код, который нужно выполнить дальше.
public interface Continuation<in T> {
public val context: CoroutineContext
public fun resumeWith(result: Result<T>)
}
// Расширения
public fun <T> Continuation<T>.resume(value: T): Unit =
resumeWith(Result.success(value))
public fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
resumeWith(Result.failure(exception))
Suspend функции: трансформация
Компилятор преобразует suspend функции:
// Исходная suspend функция
suspend fun delay(time: Long)
// Компилятор создает обычную функцию с Continuation параметром
fun delay(time: Long, continuation: Continuation<Unit>): Any
Magic return value: COROUTINE_SUSPENDED
// Suspend функция может вернуть:
// 1. Результат (если не нужна приостановка)
// 2. COROUTINE_SUSPENDED (если приостановлена)
public val COROUTINE_SUSPENDED: Any = object {}
suspend fun fetchData(): String = suspendCancellableCoroutine { cont ->
networkCall { result ->
cont.resume(result) // Возобновить корутину
}
COROUTINE_SUSPENDED // Вернуть this value
}
Dispatcher: Где выполняется код
Корутины не привязаны к потокам. Dispatcher решает где выполнить код:
// Dispatcher определяет поток
launchIO {
// Выполняется на IO потоке
val data = fetchFromNetwork() // Network IO
withContext(Dispatchers.Main) {
// Переключились на Main поток
updateUI(data)
}
withContext(Dispatchers.Default) {
// Выполняется на Default потоке
heavyComputation()
}
}
Типы Dispatcher'ов:
Main - UI поток (один поток для UI)
IO - Thread pool для IO операций (64 потока по умолчанию)
Default - Thread pool для CPU-intensive (ядра процессора)
Unconfined - Не переключает потоки (опасно!)
Custom - Свой Executor
Как работает withContext
launchIO {
println("1: ${Thread.currentThread().name}") // IO-1
withContext(Dispatchers.Main) {
println("2: ${Thread.currentThread().name}") // main
}
println("3: ${Thread.currentThread().name}") // IO-1 (вернулись обратно!)
}
Внутренний механизм:
- Корутина приостанавливается в withContext
- Код в блоке выполняется на Main диспетчере
- Сохраняется Continuation с информацией о возврате
- После завершения блока, Continuation возобновляется на IO диспетчере
Scope: Иерархия корутин
CoroutineScope организует иерархию и управление жизненным циклом:
class MyViewModel : ViewModel() {
// viewModelScope автоматически отменяется при очистке ViewModel
fun loadData() {
viewModelScope.launch {
val result = fetchData() // Отменится при очистке
}
}
}
// Структурированная параллелизм
launch {
val job1 = launch { task1() }
val job2 = launch { task2() }
// Ждёт завершения job1 и job2 перед выходом из launch блока
}
Job и Cancellation
Job отслеживает состояние корутины:
val job = launch {
try {
delay(5000)
println("Done")
} catch (e: CancellationException) {
println("Cancelled")
throw e // Переброс обязателен!
}
}
job.cancel() // Отменить корутину
job.join() // Ждать отмены
// Или вместе
job.cancelAndJoin()
Механизм отмены:
- cancel() устанавливает флаг отмены
- При следующей suspend точке выбрасывается CancellationException
- Finally блоки выполняются
- Корутина завершается
Как работает delay
// delay — это suspend функция
suspend fun delay(timeMillis: Long)
// Под капотом (упрощенно):
fun delay(timeMillis: Long, continuation: Continuation<Unit>): Any {
val scheduler = continuation.context[CoroutineDispatcher]
scheduler.scheduleBlock(timeMillis) {
continuation.resume(Unit) // Возобновить корутину
}
return COROUTINE_SUSPENDED // Приостановить
}
CoroutineContext: Управление контекстом
// CoroutineContext содержит информацию о корутине
val context: CoroutineContext = Dispatchers.Main + Job() + CoroutineExceptionHandler { _, e ->
println("Error: $e")
}
// Элементы контекста
Dispatchers.Main // Где выполнять
Job() // Управление жизненным циклом
CoroutineExceptionHandler // Обработка ошибок
CoroutineName // Имя для отладки
Практический пример: Трансформация
// Исходный код
suspend fun getUserAndPosts(userId: String): Pair<User, List<Post>> {
val user = fetchUser(userId) // suspend point
val posts = fetchPosts(userId) // suspend point
return user to posts
}
// Компилятор создает State Machine
fun getUserAndPosts(
userId: String,
completion: Continuation<Pair<User, List<Post>>>
): Any {
var state = 0
var user: User? = null
var posts: List<Post>? = null
fun resume() {
try {
when (state) {
0 -> {
state = 1
fetchUser(userId) { result ->
user = result
resume()
}
return COROUTINE_SUSPENDED
}
1 -> {
state = 2
fetchPosts(userId) { result ->
posts = result
resume()
}
return COROUTINE_SUSPENDED
}
2 -> {
completion.resume(user!! to posts!!)
}
}
} catch (e: Throwable) {
completion.resumeWithException(e)
}
}
return resume()
}
Производительность
Почему корутины быстрые:
- Не потоки - Миллионы корутин на одном потоке
- Stack-less - Не используют стек для каждой корутины
- Cheap switching - Переключение между корутинами дешево
- No context switch - Нет overhead CPU переключения контекста
Потоки: 1000 потоков = ~1GB памяти
Корутины: 1М корутин = ~100MB памяти
Вывод
Корутины под капотом:
- State Machine - Компилятор преобразует в автомат состояний
- Continuation - Объект с информацией о продолжении
- Dispatcher - Определяет поток выполнения
- Job - Управляет жизненным циклом
- COROUTINE_SUSPENDED - Магический return value
- No threads - Не привязаны к потокам
Этот механизм позволяет писать асинхронный код как синхронный, без callback hell.