← Назад к вопросам

За счет чего корутину можно возобновлять

2.3 Middle🔥 131 комментариев
#Многопоточность и асинхронность

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

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Механизм возобновления корутин

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

Основной механизм: Continuation

Continuation — это объект, который представляет «что делать дальше» в программе:

// Это то, что корутина хранит в памяти
public interface Continuation<in T> {
    val context: CoroutineContext
    fun resumeWith(result: Result<T>)
}

Когда корутина приостанавливается (на delay, сетевой запрос и т.д.), она сохраняет в памяти это самое Continuation с информацией о том, где она остановилась.

Как это работает под капотом

1. Исходный код (написал разработчик)

suspend fun loadUser(): User {
    val data = fetchFromNetwork()
    delay(1000)
    return User(data)
}

fun test() {
    launch {
        val user = loadUser()
        println(user)
    }
}

2. После трансформации компилятором (что происходит на самом деле)

fun loadUser(continuation: Continuation<User>): Any {
    // Состояние функции кодируется в переменной
    when (continuation.state) {
        0 -> {
            // Первое выполнение
            fetchFromNetwork { data ->
                // Переход в состояние 1
                continuation.state = 1
                continuation.resumeWith(Success(User(data)))
            }
            return COROUTINE_SUSPENDED
        }
        1 -> {
            // Возобновление после delay
            delay(1000, continuation) // Передаём continuation для возобновления
            return COROUTINE_SUSPENDED
        }
        2 -> {
            // Возобновление после delay — возвращаем результат
            return User(...)
        }
    }
}

Три компонента для возобновления

1. Состояние (State Machine)

Компилятор создаёт машину состояний, где каждая suspend точка — это новое состояние:

suspend fun process() {
    println("State 0")      // Состояние 0
    delay(1000)             // Suspend point → Состояние 1
    println("State 1")      // Состояние 1
    fetchData()             // Suspend point → Состояние 2
    println("State 2")      // Состояние 2
}

2. Continuation (Контекст возобновления)

Прежде чем приостановиться, корутина передаёт свой Continuation в suspend-функцию:

suspend fun delay(timeMillis: Long) {
    // Функция получает Continuation
    // После истечения времени вызовет:
    // continuation.resumeWith(Result.success(Unit))
}

3. Dispatcher (Распределитель потоков)

Когда нужно возобновить корутину, Dispatcher решает, на каком потоке это сделать:

launch(Dispatchers.Main) {
    delay(1000) // Приостановится
    // Возобновится на Main потоке
    println("На UI потоке")
}

launch(Dispatchers.IO) {
    val data = api.fetch() // Приостановится
    // Возобновится на IO потоке
    println(data)
}

Пример: как delay работает

fun testDelay() {
    launch {
        println("1. Start: ${Thread.currentThread().name}")
        delay(2000) // SUSPEND POINT
        println("2. Resume: ${Thread.currentThread().name}")
    }
}

// Что происходит внутри:
// 1. launch создаёт Job и Continuation
// 2. Начинает выполнение корутины
// 3. Встречает delay(2000)
// 4. delay получает Continuation и сохраняет его
// 5. Возвращает COROUTINE_SUSPENDED
// 6. Корутина НЕ занимает поток (поток свободен!)
// 7. Через 2 секунды Handler на Main потоке вызывает:
//    continuation.resumeWith(Result.success(Unit))
// 8. Корутина возобновляется на Main потоке
// 9. Выполняет println("2. Resume...")

Сохранение локальных переменных

Когда корутина приостанавливается, все локальные переменные сохраняются:

suspend fun example() {
    val user = loadUser()  // Переменная сохраняется
    delay(1000)            // SUSPEND: user всё ещё в памяти
    val posts = loadPosts(user) // Используем user после возобновления
}

// После компиляции:
fun example(continuation: Continuation) {
    val locals = ContinuationImpl(...) // Объект для хранения переменных
    locals.user = loadUser()
    delay(1000) {
        locals.posts = loadPosts(locals.user)
    }
}

Что происходит при исключении

suspend fun failingFunction() {
    delay(1000)
    throw Exception("Ошибка")
}

fun test() {
    launch {
        try {
            failingFunction() // SUSPEND
        } catch (e: Exception) {
            println(e.message) // Будет выпечатано
        }
    }
}

// Внутри: 
// continuation.resumeWithException(Exception("Ошибка"))

Реальный пример: Repository

class UserRepository {
    suspend fun getUser(id: Int): User {
        return api.fetchUser(id) // Suspend point
    }
}

class UserViewModel : ViewModel() {
    fun loadUser(id: Int) {
        viewModelScope.launch {
            try {
                val user = repository.getUser(id) // SUSPEND
                // После возобновления:
                state.value = user // Обновляем UI
            } catch (e: Exception) {
                state.value = Error(e)
            }
        }
    }
}

Важные моменты

1. Корутина НЕ занимает поток

// Можно запустить миллионы корутин
repeat(1_000_000) {
    launch {
        delay(1000)
        println("Done $it")
    }
}
// На 1000 потоков это вызовет OutOfMemoryError,
// но на 1000000 корутин работает отлично!

2. Возобновление может быть на другом потоке

launch(Dispatchers.Main) {
    val result = withContext(Dispatchers.IO) {
        api.fetch() // SUSPEND на IO потоке
    }
    // Возобновляется на Main потоке!
    updateUI(result)
}

3. Nested корутины

suspend fun outer() {
    val result = inner() // SUSPEND, ждёт результат
    println(result)
}

suspend fun inner(): String {
    delay(1000)
    return "Done"
}

Итог

Корутины возобновляются благодаря:

  • Continuation API — сохранение контекста возобновления
  • State Machine — компилятор преобразует suspend-функции в машины состояний
  • Dispatcher — решает, на каком потоке возобновить
  • Локальные переменные — сохраняются в памяти между приостановками
  • Non-blocking — корутина не занимает поток, пока приостановлена