Suspend function — это функция с модификатором suspend, которая может приостанавливать своё выполнение без блокировки потока и возобновляться позже в той же корутине. По данным JetBrains Kotlin Docs, 2025, suspend-функции являются фундаментальным строительным блоком корутин, обеспечивающим асинхронность без callback-ов. Каждая suspend-функция компилируется в state-машину на базе Continuation, что позволяет эффективно управлять точками приостановки.
Главное
Suspend function — это функция, объявленная с ключевым словом suspend, которая может приостанавливать выполнение в одной или нескольких точках, не блокируя поток. Каждый вызов suspend-функции внутри другой suspend-функции является потенциальной точкой приостановки.
suspend fun fetchUserData(): User {
val response = httpClient.get("/user")
return parser.parse(response)
}
Компилятор Kotlin транслирует такую функцию в state-машину. Каждая точка приостановки (вызов другой suspend-функции) становится состоянием (label). Текущий поток освобождается между состояниями, а после завершения ожидаемой операции выполнение возобновляется со следующего состояния.
Suspend-функции появились в Kotlin 1.3 (2018 год) вместе с корутинами как экспериментальная фича и стали стабильными в Kotlin 1.5 (2021 год). До этого асинхронность в Kotlin/Java обеспечивалась через callback-и, RxJava и CompletableFuture. Suspend-функции предложили альтернативу с линейным синтаксисом и автоматическим управлением потоками.
Понимание внутреннего устройства suspend-функций — ключ к правильной работе с корутинами. В отличие от обычных функций, каждая suspend-функция компилируется в класс с интерфейсом Continuation.
Компилятор Kotlin добавляет параметр типа Continuation в конец каждого параметра suspend-функции. Continuation содержит:
Допустим, у нас есть suspend-функция с двумя вызовами других suspend-функций:
suspend fun process() {
val a = stepOne()
val b = stepTwo(a)
println(b)
}
Компилятор преобразует её в state-машину с метками:
// Simplified generated code representation
fun process(cont: Continuation<Unit>): Any? {
val cont = cont as ProcessContinuation
when (cont.label) {
0 -> {
cont.label = 1
if (stepOne(cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
}
1 -> {
cont.label = 2
val a = cont.result as TypeA
if (stepTwo(a, cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
}
2 -> {
println(cont.result)
Unit
}
}
}
Ключевое наблюдение: если функция возвращает COROUTINE_SUSPENDED, текущий поток освобождается. Когда асинхронная операция завершается, Continuation.resumeWith вызывается, и state-машина продолжает со следующего label.
Объявление suspend-функции ничем не отличается от обычной, кроме ключевого слова suspend перед fun. Ограничение только одно: вызывать suspend-функцию можно только из корутины или другой suspend-функции.
suspend fun delayAndReturn(ms: Long): String {
delay(ms)
return "Done after ${ms}ms"
}
В этом примере delay — тоже suspend-функция, которая приостанавливает корутину на указанное количество миллисекунд, не блокируя поток. После задержки выполнение возобновляется.
fun main() = runBlocking {
val result = delayAndReturn(1000)
println(result)
}
runBlocking создаёт мост между обычным миром и корутинами. Внутри лямбды доступен вызов любых suspend-функций.
Kotlin поддерживает suspend-версии функциональных типов — suspend () -> T и suspend (A) -> B. Это позволяет передавать асинхронные лямбды в функции высшего порядка:
suspend fun withRetry (
retries: Int = 3,
block: suspend () -> T
): T {
repeat(retries - 1) {
try { return block() }
catch (_: Exception) { delay(100) }
}
return block()
}
Функция withRetry принимает suspend-лямбду и повторяет её выполнение при ошибках. Это типичный паттерн для сетевых запросов с ретраями.
Различия между suspend и обычными функциями выходят за рамки простого добавления модификатора. Рассмотрим принципиальные отличия.
| Характеристика | Обычная функция | Suspend-функция |
|---|---|---|
| Поток выполнения | Блокирует поток до завершения | Может освободить поток и возобновиться позже |
| Параметры компилятора | Только указанные параметры | Неявный Continuation в конце |
| Вызов из обычной функции | Да | Нет |
| Стек | Физический стек потока | State-машина в heap + физический стек между точками |
| Возврат | Непосредственное значение | Значение или COROUTINE_SUSPENDED |
| Производительность | Минимальные накладные расходы | ~несколько наносекунд на state-машину (Kotlin 1.9+) |
Обычная функция не имеет Continuation — ей некуда сохранить состояние и нечем возобновить выполнение. Если нужно вызвать suspend-функцию из обычной, используйте runBlocking (для тестов) или CoroutineScope.launch (для production с учётом lifecycle).
Рассмотрим три реальных сценария использования suspend-функций в Android-приложениях на Kotlin.
Room поддерживает suspend-функции напрямую — запрос выполняется в фоновом потоке автоматически:
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUser(id: Int): User?
@Insert
suspend fun insertUser(user: User)
}
Room внутренне использует Dispatchers.IO для выполнения запроса, а результат возвращается в тот диспетчер, на котором была вызвана suspend-функция.
class ProfileViewModel : ViewModel() {
private val repo = ProfileRepository()
fun loadProfile(id: String) {
viewModelScope.launch {
val profile = repo.getProfile(id)
_profile.value = profile
}
}
}
ViewModelScope.launch создаёт корутину, внутри которой вызывается suspend-функция getProfile. После получения результата UI обновляется на главном потоке.
suspend fun placeOrder(cart: Cart): OrderResult {
val validated = validateCart(cart)
val payment = processPayment(validated)
val receipt = sendReceipt(payment)
return receipt
}
Три suspend-функции выполняются последовательно. На каждом шаге корутина может приостановиться, не блокируя поток. Если какой-то шаг выбросит исключение — остальные не выполнятся, что защищает от некорректных состояний заказа.
Даже опытные разработчики Kotlin допускают ошибки при проектировании suspend-функций. Рассмотрим самые частые.
Suspend-функция не делает код автоматически асинхронным. Thread.sleep(), InputStream.read() и другие блокирующие вызовы всё равно заблокируют поток. Используйте withContext(Dispatchers.IO) для оборачивания блокирующих операций.
Если функция не вызывает других suspend-функций и не выполняет асинхронных операций — модификатор suspend избыточен. Он добавляет накладные расходы на state-машину и ограничивает контекст вызова. Делайте функцию suspend только когда она действительно приостанавливается.
При отмене корутины suspend-функции выбрасывают CancellationException. Не ловите его бездумно — вы лишаете вызывающий код возможности корректно завершить отмену. Если нужно выполнить финализирующую операцию, используйте finally блок и NonCancellable.
suspend fun safeOperation() {
try {
doWork()
} finally {
withContext(NonCancellable) {
cleanup()
}
}
}
Блок finally выполняется всегда, включая отмену, а NonCancellable гарантирует, что cleanup не прервётся.
Нельзя вызвать suspend-функцию напрямую из callback без создания корутины. Используйте suspendCoroutine или suspendCancellableCoroutine для адаптации callback-стиля к корутинам.
Часто задаваемые вопросы
Да, технически suspend-функция может не вызывать других suspend-функций. Компилятор создаст state-машину с одним состоянием (label 0). Однако практической пользы в такой функции нет — она выполняется как обычная, но с накладными расходами. Не используйте suspend без необходимости.
Kotlin предоставляет kotlinx-coroutines-debug — библиотеку с DebugProbes и инструментами трассировки корутин. В Android Studio начиная с версии Arctic Fox есть встроенная вкладка Coroutines в Debugger, которая показывает активные корутины, их состояние и точки приостановки.
Каждая точка приостановки создаёт новое состояние в state-машине. Для большинства приложений накладные расходы одной точки составляют единицы наносекунд (Kotlin 1.9+). Только при десятках тысяч точек в цикле стоит объединять операции или использовать sequence/flow.
В Kotlin suspend — модификатор типа функции, а не маркер возвращаемого значения (как async в C#). Любая suspend-функция может иметь любые параметры и возвращаемый тип, и её вызов синтаксически не отличается от вызова обычной функции — нет await-оператора на месте вызова.
Используйте suspendCancellableCoroutine для адаптации. Внутри вы передаёте регистрацию callback, которая вызывает continuation.resume(), и возвращаете токен отмены, если callback поддерживает отписку. Это стандартный паттерн для обёртки старых Android API.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также