Как работает 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-функции или в месте её вызова.