Как работает suspend-функция?

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

Что такое suspend

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

Пример простой suspend-функции:

suspend fun fetchData(): String {
delay(1000) // симулируем запрос к сети
return "data"
}

delay(1000) — это приостановка (suspension), не блокирующая поток. Поток, на котором запущена корутина, освобождается и может выполнять другие задачи.

Как вызывается suspend-функция

suspend-функции можно вызывать:

  • Из другой suspend-функции

  • Изнутри корутины (launch, async)

  • Через withContext или runBlocking (в main() или unit-тестах)

Пример:

fun main() = runBlocking {
val result = fetchData()
println(result)
}

Отличие от обычной функции

Обычная функция suspend-функция
Не может быть приостановлена Может приостановить выполнение
--- ---
Блокирует поток при задержке Поток остаётся свободным
--- ---
Нельзя вызывать delay() и другие suspend-функции Можно вызывать suspend-функции
--- ---

Что делает suspend-функцию приостанавливаемой

suspend-функция сама по себе ничего не приостанавливает, если внутри неё не вызывается другая suspend-функция или не используется механизм приостановки. Ключевую роль играют такие функции как:

  • delay() — задержка без блокировки потока

  • withContext() — смена контекста (диспетчера)

  • await() — ожидание результата Deferred

  • channel.receive() — ожидание сообщения из канала

Пример:

suspend fun mySuspendFunction() {
println("Перед задержкой")
delay(2000)
println("После задержки")
}

Что происходит внутри: Continuation и State Machine

Когда компилятор Kotlin видит suspend-функцию, он трансформирует её в конечный автомат (state machine) и использует Continuation Passing Style (CPS). То есть он разбивает функцию на отдельные состояния, между которыми происходит переход при возобновлении.

Каждая точка, где есть suspend, — это потенциальная точка приостановки. После приостановки выполнение откладывается, и компилятор сохраняет всё необходимое состояние (переменные, место остановки) в объекте типа Continuation.

Когда выполнение возобновляется, состояние восстанавливается из continuation и продолжается с той точки, на которой было остановлено.

Пример разбиения suspend-функции на состояния

Функция:

suspend fun example() {
println("A")
delay(1000)
println("B")
}

Под капотом превращается примерно в такую логику:

when (state) {
0 -> {
println("A")
state = 1
return delay(1000, continuation) // приостановка
}
1 -> {
println("B")
state = DONE
}
}

Это позволяет Kotlin эффективно управлять вызовами, даже если они занимают длительное время или зависят от внешних ресурсов.

Особенности вызова

Вызов из корутины:

launch {
fetchData()
}

Вызов из runBlocking:

fun main() = runBlocking {
fetchData()
}

Вызов с withContext:

val result = withContext(Dispatchers.IO) {
fetchData()
}

Пример с несколькими приостановками

suspend fun fetchFromNetwork(): String {
delay(1000)
return "Network Data"
}
suspend fun saveToDatabase(data: String) {
delay(500)
println("Сохранено: $data")
}
suspend fun loadData() {
val data = fetchFromNetwork()
saveToDatabase(data)
}

В этом примере:

  • Первая приостановка на delay(1000)
  • Вторая — на delay(500)
  • Всё это выполняется без блокировки потоков

Можно ли написать suspend без delay?

Да, suspend-функция может не содержать delay или других приостановок. Это допустимо, но в таком случае она не будет приостанавливать выполнение, то есть будет вести себя как обычная функция. Пример:

suspend fun compute(): Int {
return 42
}

Преимущества использования suspend-функций

  • Читаемость: код выглядит как обычный последовательный код

  • Безопасность: благодаря structured concurrency корутины не теряются

  • Производительность: не блокируют потоки

  • Масштабируемость: можно запускать тысячи таких операций одновременно

  • Контроль: можно отменять выполнение, управлять временем, задавать контекст

Важно знать

  • suspend-функции не возвращают Future или Promise, как в JavaScript — они возвращают просто T, а корутина заботится о том, когда именно она выполнится

  • Они не могут вызываться из обычных функций напрямую — нужен suspend-контекст

  • Использование suspend в функции не означает, что она обязательно делает что-то асинхронное — это просто возможность приостановки, а не обязанность

Пример с ошибками и suspend

suspend-функции обрабатывают исключения как обычные функции:

suspend fun riskyCall() {
delay(1000)
throw RuntimeException("Ошибка!")
}
fun main() = runBlocking {
try {
riskyCall()
} catch (e: Exception) {
println("Перехвачено: $e")
}
}

Исключения могут быть обработаны внутри suspend-функции или в месте её вызова.