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-машина с етикети:
// Опростено представяне на генериран код
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 (за продукция, като вземете предвид жизнения цикъл).
Нека разгледаме три реални сценария за използване на 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 гарантира, че почистването няма да бъде прекъснато.
Не може да се извика 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също