suspend function: що це, синтаксис і робота в корутинах

Автор: IT Sectr Опубліковано: 2026-06-22 Час читання: 9 хв

Suspend function — це функція з модифікатором suspend, яка може призупиняти своє виконання без блокування потоку та відновлюватися пізніше в тій самій корутині. За даними JetBrains Kotlin Docs, 2025, suspend-функції є фундаментальним будівельним блоком корутин, що забезпечує асинхронність без callback-ів. Кожна suspend-функція компілюється в state-машину на базі Continuation, що дозволяє ефективно керувати точками призупинення.

Головне

  • Suspend — ключове слово Kotlin, яке позначає функцію як призупинювану (асинхронну)
  • Continuation — прихований параметр, який компілятор додає в кожну suspend-функцію для збереження стану
  • Точки призупинення — місця виклику інших suspend-функцій, де корутина може зупинитися без блокування
  • State-машина — внутрішнє представлення suspend-функції, де кожна точка призупинення — окремий state
  • Виклик тільки з корутини — suspend-функції можна викликати тільки з іншої suspend-функції або з launch/async

Що таке suspend function у Kotlin?

Suspend function — це функція, оголошена з ключовим словом suspend, яка може призупиняти виконання в одній або кількох точках, не блокуючи потік. Кожен виклик suspend-функції всередині іншої suspend-функції є потенційною точкою призупинення.

kotlin
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-функції: Continuation і state-машина

Розуміння внутрішнього устрою suspend-функцій — ключ до правильної роботи з корутинами. На відміну від звичайних функцій, кожна suspend-функція компілюється в клас з інтерфейсом Continuation.

Continuation — прихований параметр

Компілятор Kotlin додає параметр типу Continuation в кінець кожного параметра suspend-функції. Continuation містить:

  • context — CoroutineContext (диспетчер, job, елементи контексту)
  • resumeWith — метод для відновлення виконання з результатом або винятком
  • label — індекс поточного стану в state-машині

State-машина на прикладі

Припустимо, у нас є suspend-функція з двома викликами інших suspend-функцій:

kotlin
suspend fun process() {
    val a = stepOne()
    val b = stepTwo(a)
    println(b)
}

Компілятор перетворює її в state-машину з мітками:

kotlin
// 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-функції нічим не відрізняється від звичайної, крім ключового слова suspend перед fun. Обмеження тільки одне: викликати suspend-функцію можна тільки з корутини або іншої suspend-функції.

Базове оголошення

kotlin
suspend fun delayAndReturn(ms: Long): String {
    delay(ms)
    return "Done after ${ms}ms"
}

У цьому прикладі delay — теж suspend-функція, яка призупиняє корутину на вказану кількість мілісекунд, не блокуючи потік. Після затримки виконання відновлюється.

Виклик із корутини

kotlin
fun main() = runBlocking {
    val result = delayAndReturn(1000)
    println(result)
}

runBlocking створює міст між звичайним світом і корутинами. Усередині лямбди доступний виклик будь-яких suspend-функцій.

Suspend-лямбди та функціональні типи

Kotlin підтримує suspend-версії функціональних типів — suspend () -> T та suspend (A) -> B. Це дозволяє передавати асинхронні лямбди в функції вищого порядку:

kotlin
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 і звичайними функціями виходять за рамки простого додавання модифікатора. Розглянемо принципові відмінності.

ХарактеристикаЗвичайна функціяSuspend-функція
Потік виконанняБлокує потік до завершенняМоже звільнити потік і відновитися пізніше
Параметри компілятораТільки вказані параметриНеявний Continuation в кінці
Виклик зі звичайної функціїТакНі
СтекФізичний стек потокуState-машина в heap + фізичний стек між точками
ПоверненняБезпосереднє значенняЗначення або COROUTINE_SUSPENDED
ПродуктивністьМінімальні накладні витрати~кілька наносекунд на state-машину (Kotlin 1.9+)

Чому suspend-функції не можна викликати зі звичайних

Звичайна функція не має Continuation — їй нікуди зберегти стан і нічим відновити виконання. Якщо потрібно викликати suspend-функцію зі звичайної, використовуйте runBlocking (для тестів) або CoroutineScope.launch (для production з урахуванням lifecycle).

Приклади suspend-функцій в Android

Розглянемо три реальних сценарії використання suspend-функцій в Android-додатках на Kotlin.

Приклад 1: Room DAO з suspend-запитами

Room підтримує suspend-функції безпосередньо — запит виконується у фоновому потоці автоматично:

kotlin
@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-функція.

Приклад 2: Композиція suspend-функцій для завантаження екрану

kotlin
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 оновлюється на головному потоці.

Приклад 3: Послідовні асинхронні кроки

kotlin
suspend fun placeOrder(cart: Cart): OrderResult {
    val validated = validateCart(cart)
    val payment = processPayment(validated)
    val receipt = sendReceipt(payment)
    return receipt
}

Три suspend-функції виконуються послідовно. На кожному кроці корутина може призупинитися, не блокуючи потік. Якщо якийсь крок викине виняток — інші не виконаються, що захищає від некоректних станів замовлення.

Типові помилки при роботі з suspend-функціями

Навіть досвідчені розробники Kotlin допускають помилки при проектуванні suspend-функцій. Розглянемо найчастіші.

Помилка 1: Блокуючі виклики всередині suspend

Suspend-функція не робить код автоматично асинхронним. Thread.sleep(), InputStream.read() та інші блокуючі виклики все одно заблокують потік. Використовуйте withContext(Dispatchers.IO) для обгортання блокуючих операцій.

Помилка 2: Створення suspend-функцій без необхідності

Якщо функція не викликає інших suspend-функцій і не виконує асинхронних операцій — модифікатор suspend надлишковий. Він додає накладні витрати на state-машину та обмежує контекст виклику. Робіть функцію suspend тільки коли вона дійсно призупиняється.

Помилка 3: Ігнорування CancellationException

При скасуванні корутини suspend-функції викидають CancellationException. Не ловіть його бездумно — ви позбавляєте код, що викликає, можливості коректно завершити скасування. Якщо потрібно виконати фіналізуючу операцію, використовуйте finally блок і NonCancellable.

kotlin
suspend fun safeOperation() {
    try {
        doWork()
    } finally {
        withContext(NonCancellable) {
            cleanup()
        }
    }
}

Блок finally виконується завжди, включаючи скасування, а NonCancellable гарантує, що cleanup не перерветься.

Помилка 4: Виклик suspend-функцій з callback-ів

Не можна викликати suspend-функцію безпосередньо з callback без створення корутини. Використовуйте suspendCoroutine або suspendCancellableCoroutine для адаптації callback-стилю до корутин.

Часто задавані питання

Чи може suspend-функція не містити точок призупинення?

Так, технічно suspend-функція може не викликати інших suspend-функцій. Компілятор створить state-машину з одним станом (label 0). Однак практичної користі в такій функції немає — вона виконується як звичайна, але з накладними витратами. Не використовуйте suspend без необхідності.

Як налагоджувати suspend-функції?

Kotlin надає kotlinx-coroutines-debug — бібліотеку з DebugProbes та інструментами трасування корутин. В Android Studio починаючи з версії Arctic Fox є вбудована вкладка Coroutines у Debugger, яка показує активні корутини, їх стан і точки призупинення.

Чи впливає кількість suspend-точок на продуктивність?

Кожна точка призупинення створює новий стан у state-машині. Для більшості додатків накладні витрати однієї точки становлять одиниці наносекунд (Kotlin 1.9+). Тільки при десятках тисяч точок у циклі варто об'єднувати операції або використовувати sequence/flow.

Чим suspend-функція відрізняється від async/await в інших мовах?

У Kotlin suspend — модифікатор типу функції, а не маркер значення, що повертається (як async у C#). Будь-яка suspend-функція може мати будь-які параметри та тип, що повертається, і її виклик синтаксично не відрізняється від виклику звичайної функції — немає await-оператора на місці виклику.

Як перетворити callback-функцію на suspend?

Використовуйте suspendCancellableCoroutine для адаптації. Всередині ви передаєте реєстрацію callback, який викликає continuation.resume(), і повертаєте токен скасування, якщо callback підтримує відписку. Це стандартний патерн для обгортки старих Android API.

Підсумки

  • Suspend function — функція з модифікатором suspend, яка може призупиняти виконання без блокування потоку через механізм Continuation
  • State-машина — внутрішнє представлення suspend-функції в байткоді Kotlin, де кожна точка призупинення — окремий стан з label
  • Continuation — прихований параметр, що містить контекст корутини та метод resumeWith для відновлення виконання
  • Виклик тільки з корутини — suspend-функції недоступні зі звичайних функцій без runBlocking або CoroutineScope
  • Блокуючі операції всередині suspend вимагають withContext(Dispatchers.IO) — інакше потік блокується
  • Room і Retrofit підтримують suspend-функції нативно, автоматично керуючи фоновими потоками
  • CancellationException — обробляйте скасування через finally + NonCancellable, не ловіть CancellationException бездумно

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також