Suspend function — to funkcja z modyfikatorem suspend, która może wstrzymywać swoje wykonanie bez blokowania wątku i wznawiać je później w tej samej korutynie. Według JetBrains Kotlin Docs, 2025, funkcje suspendujące są fundamentalnym budulcem korutyn, zapewniającym asynchroniczność bez callbacków. Każda funkcja suspendująca jest kompilowana do maszyny stanów opartej na Continuation, co pozwala efektywnie zarządzać punktami wstrzymania.
Najważniejsze
Suspend function — to funkcja zadeklarowana ze słowem kluczowym suspend, która może wstrzymywać wykonanie w jednym lub kilku punktach bez blokowania wątku. Każde wywołanie funkcji suspendującej wewnątrz innej funkcji suspendującej jest potencjalnym punktem wstrzymania.
suspend fun fetchUserData(): User {
val response = httpClient.get("/user")
return parser.parse(response)
}
Kompilator Kotlin tłumaczy taką funkcję na maszynę stanów. Każdy punkt wstrzymania (wywołanie innej funkcji suspendującej) staje się stanem (label). Bieżący wątek jest zwalniany między stanami, a po zakończeniu oczekiwanej operacji wykonanie wznawia się od następnego stanu.
Funkcje suspendujące pojawiły się w Kotlin 1.3 (2018 rok) wraz z korutynami jako eksperymentalna funkcja i stały się stabilne w Kotlin 1.5 (2021 rok). Wcześniej asynchroniczność w Kotlin/Java była zapewniana przez callbacki, RxJava i CompletableFuture. Funkcje suspendujące zaproponowały alternatywę z liniową składnią i automatycznym zarządzaniem wątkami.
Zrozumienie wewnętrznej budowy funkcji suspendujących to klucz do prawidłowej pracy z korutynami. W przeciwieństwie do zwykłych funkcji, każda funkcja suspendująca jest kompilowana do klasy z interfejsem Continuation.
Kompilator Kotlin dodaje parametr typu Continuation na końcu każdego parametru funkcji suspendującej. Continuation zawiera:
Zakładając, że mamy funkcję suspendującą z dwoma wywołaniami innych funkcji suspendujących:
suspend fun process() {
val a = stepOne()
val b = stepTwo(a)
println(b)
}
Kompilator przekształca ją w maszynę stanów z etykietami:
// Uproszczona reprezentacja wygenerowanego kodu
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
}
}
}
Kluczowe spostrzeżenie: jeśli funkcja zwraca COROUTINE_SUSPENDED, bieżący wątek jest zwalniany. Gdy operacja asynchroniczna się zakończy, wywoływane jest Continuation.resumeWith, a maszyna stanów kontynuuje od następnego labelu.
Deklaracja funkcji suspendującej niczym nie różni się od zwykłej, poza słowem kluczowym suspend przed fun. Ograniczenie jest tylko jedno: funkcję suspendującą można wywołać tylko z korutyny lub innej funkcji suspendującej.
suspend fun delayAndReturn(ms: Long): String {
delay(ms)
return "Done after ${ms}ms"
}
W tym przykładzie delay to także funkcja suspendująca, która wstrzymuje korutynę na określoną liczbę milisekund bez blokowania wątku. Po opóźnieniu wykonanie zostaje wznowione.
fun main() = runBlocking {
val result = delayAndReturn(1000)
println(result)
}
runBlocking tworzy most między zwykłym światem a korutynami. Wewnątrz lambdy dostępne jest wywoływanie dowolnych funkcji suspendujących.
Kotlin obsługuje suspendujące wersje typów funkcyjnych — suspend () -> T i suspend (A) -> B. Pozwala to przekazywać asynchroniczne lambdy do funkcji wyższego rzędu:
suspend fun withRetry (
retries: Int = 3,
block: suspend () -> T
): T {
repeat(retries - 1) {
try { return block() }
catch (_: Exception) { delay(100) }
}
return block()
}
Funkcja withRetry przyjmuje lambdę suspendującą i powtarza jej wykonanie w przypadku błędów. To typowy wzorzec dla zapytań sieciowych z ponawianiem.
Różnice między suspend a zwykłymi funkcjami wykraczają poza proste dodanie modyfikatora. Rozważmy zasadnicze różnice.
| Cecha | Zwykła funkcja | Funkcja suspendująca |
|---|---|---|
| Wątek wykonania | Blokuje wątek do zakończenia | Może zwolnić wątek i wznowić później |
| Parametry kompilatora | Tylko określone parametry | Niejawny Continuation na końcu |
| Wywołanie ze zwykłej funkcji | Tak | Nie |
| Stos | Fizyczny stos wątku | Maszyna stanów na stercie + fizyczny stos między punktami |
| Zwracanie | Bezpośrednia wartość | Wartość lub COROUTINE_SUSPENDED |
| Wydajność | Minimalne narzuty | ~kilka nanosekund na maszynę stanów (Kotlin 1.9+) |
Zwykła funkcja nie ma Continuation — nie ma gdzie zapisać stanu ani czym wznowić wykonania. Jeśli potrzebujesz wywołać funkcję suspendującą ze zwykłej, użyj runBlocking (do testów) lub CoroutineScope.launch (dla produkcji z uwzględnieniem lifecycle).
Rozważmy trzy rzeczywiste scenariusze użycia funkcji suspendujących w aplikacjach Android w Kotlin.
Room obsługuje funkcje suspendujące bezpośrednio — zapytanie jest wykonywane w tle automatycznie:
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUser(id: Int): User?
@Insert
suspend fun insertUser(user: User)
}
Room wewnętrznie używa Dispatchers.IO do wykonania zapytania, a wynik jest zwracany do tego dyspozytora, w którym została wywołana funkcja suspendująca.
class ProfileViewModel : ViewModel() {
private val repo = ProfileRepository()
fun loadProfile(id: String) {
viewModelScope.launch {
val profile = repo.getProfile(id)
_profile.value = profile
}
}
}
ViewModelScope.launch tworzy korutynę, wewnątrz której wywoływana jest funkcja suspendująca getProfile. Po uzyskaniu wyniku interfejs jest aktualizowany na głównym wątku.
suspend fun placeOrder(cart: Cart): OrderResult {
val validated = validateCart(cart)
val payment = processPayment(validated)
val receipt = sendReceipt(payment)
return receipt
}
Trzy funkcje suspendujące są wykonywane sekwencyjnie. Na każdym kroku korutyna może się zatrzymać bez blokowania wątku. Jeśli któryś krok zgłosi wyjątek — pozostałe nie zostaną wykonane, co chroni przed nieprawidłowymi stanami zamówienia.
Nawet doświadczeni programiści Kotlin popełniają błędy przy projektowaniu funkcji suspendujących. Rozważmy najczęstsze.
Funkcja suspendująca nie czyni kodu automatycznie asynchronicznym. Thread.sleep(), InputStream.read() i inne wywołania blokujące nadal zablokują wątek. Używaj withContext(Dispatchers.IO) do opakowywania operacji blokujących.
Jeśli funkcja nie wywołuje innych funkcji suspendujących i nie wykonuje operacji asynchronicznych — modyfikator suspend jest zbędny. Dodaje narzuty na maszynę stanów i ogranicza kontekst wywołania. Używaj modyfikatora suspend tylko gdy funkcja naprawdę się wstrzymuje.
Przy anulowaniu korutyny funkcje suspendujące rzucają CancellationException. Nie łap go bezmyślnie — pozbawiasz kod wywołujący możliwości poprawnego zakończenia anulowania. Jeśli potrzebujesz wykonać operację finalizującą, użyj bloku finally i NonCancellable.
suspend fun safeOperation() {
try {
doWork()
} finally {
withContext(NonCancellable) {
cleanup()
}
}
}
Blok finally wykonuje się zawsze, włączając anulowanie, a NonCancellable gwarantuje, że cleanup nie zostanie przerwany.
Nie można wywołać funkcji suspendującej bezpośrednio z callbacka bez utworzenia korutyny. Używaj suspendCoroutine lub suspendCancellableCoroutine do adaptacji stylu callbackowego do korutyn.
Często zadawane pytania
Tak, technicznie funkcja suspendująca może nie wywoływać innych funkcji suspendujących. Kompilator utworzy maszynę stanów z jednym stanem (label 0). Jednak praktycznej korzyści z takiej funkcji nie ma — wykonuje się jak zwykła, ale z narzutami. Nie używaj suspend bez potrzeby.
Kotlin udostępnia kotlinx-coroutines-debug — bibliotekę z DebugProbes i narzędziami do śledzenia korutyn. W Android Studio od wersji Arctic Fox dostępna jest wbudowana zakładka Coroutines w Debugger, która pokazuje aktywne korutyny, ich stan i punkty wstrzymania.
Każdy punkt wstrzymania tworzy nowy stan w maszynie stanów. Dla większości aplikacji narzut jednego punktu to jednostki nanosekund (Kotlin 1.9+). Dopiero przy dziesiątkach tysięcy punktów w pętli warto łączyć operacje lub używać sequence/flow.
W Kotlin suspend to modyfikator typu funkcji, a nie znacznik zwracanej wartości (jak async w C#). Każda funkcja suspendująca może mieć dowolne parametry i typ zwracany, a jej wywołanie składniowo nie różni się od wywołania zwykłej funkcji — nie ma operatora await w miejscu wywołania.
Użyj suspendCancellableCoroutine do adaptacji. Wewnątrz przekazujesz rejestrację callbacka, która wywołuje continuation.resume(), i zwracasz token anulowania, jeśli callback obsługuje rezygnację z subskrypcji. To standardowy wzorzec do opakowywania starych API Androida.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również