Coroutines (korutyny) to lekkie wątki Kotlin do programowania asynchronicznego, dostępne przez bibliotekę kotlinx.coroutines. Według JetBrains Kotlin Documentation, 2026, Coroutines pozwalają wstrzymać wykonanie funkcji bez blokowania wątku, w przeciwieństwie do tradycyjnych Thread. Korutyny uruchamiane są na ograniczonej puli wątków, co czyni je tysiąc razy lżejszymi od natywnych wątków. Kotlin Coroutines są w pełni zintegrowane z Android Jetpack, Retrofit, Room i innymi popularnymi bibliotekami ekosystemu Android.
Najważniejsze
Coroutines (korutyny) to mechanizm programowania asynchronicznego w Kotlin, zaimplementowany w bibliotece kotlinx.coroutines. W przeciwieństwie do wątków systemu operacyjnego, korutyny nie są przypisane do konkretnego wątku: mogą wstrzymywać się na jednym wątku i wznawiać na innym. Jeden wątek może wykonywać tysiące korutyn, przełączając się między nimi z minimalnym narzutem.
Korutyny pojawiły się w Kotlin 1.3 (2018) jako funkcja eksperymentalna i stały się stabilne w Kotlin 1.5 (2021). Coroutines rozwiązują problem callback hell podobnie jak async/await, ale oferują bogatsze API: kanały (Channel), Flow, obsługę wyjątków w hierarchii Job oraz bezpośrednią integrację z Android Lifecycle.
Według JetBrains (2025), każda korutyna zużywa około 100 bajtów pamięci wobec 1+ MB dla natywnego wątku. Pozwala to uruchamiać miliony korutyn w jednej aplikacji bez ryzyka OutOfMemoryError. To właśnie lekkość korutyn czyni je preferowanym narzędziem do asynchroniczności w Android.
Każda korutyna Kotlin jest kompilowana do maszyny stanów poprzez Continuation Passing Style (CPS). Kompilator dodaje ukryty parametr Continuation do każdej suspend function. Continuation zawiera punkt wznowienia i wszystkie zmienne lokalne. Gdy korutyna się wstrzymuje, runtime zapisuje Continuation, a gdy wznawia — przywraca go na dowolnym dostępnym wątku z puli Dispatcher.
suspend — słowo kluczowe Kotlin oznaczające funkcję jako wstrzymywalną. Taką funkcję można wywołać tylko z innej suspend function lub z korutyny. Wewnątrz suspend function można wywoływać inne suspend functions w dowolnej kolejności, a każdy punkt wywołania to potencjalny punkt wstrzymania.
Mechanika jest prosta: gdy suspend function wywołuje inną suspend function, wstrzymuje się w tym punkcie, zwalniając wątek. Po zakończeniu wywołanej funkcji runtime kontynuuje wykonanie z zapisanego miejsca. Nazywa się to cooperative cancellation — żadne wątki nie są blokowane.
Ważne: suspend function nie jest domyślnie asynchroniczna. Kolejność wykonania pozostaje sekwencyjna, jeśli nie użyto launch lub async. suspend pozwala jedynie funkcji być wstrzymaną bez blokowania wątku i być częścią kontekstu korutyny. Continuation Passing Style to model kompilacji, w którym każda suspend function otrzymuje ukryty callback Continuation, a kompilator generuje automat stanów do zarządzania wstrzymaniami i wznowieniami.
CoroutineScope to kontekst określający cykl życia korutyn. Wszystkie korutyny muszą być uruchomione wewnątrz scope. Gdy scope jest anulowany (np. przy zakończeniu Activity), wszystkie jego korutyny potomne są automatycznie anulowane. Zapobiega to wyciekom zadań w tle. Android Jetpack dostarcza gotowe scope dla każdego komponentu: viewModelScope dla ViewModel i lifecycleScope dla Activity i Fragment, które automatycznie anulują się przy zniszczeniu odpowiedniego komponentu.
Structured Concurrency — zasada gwarantująca, że korutyna nie zakończy się, dopóki nie zakończą się wszystkie jej korutyny potomne. Hierarchia Job tworzy drzewo: główna korutyna tworzy parent job, potomne — child jobs. Anulowanie parent job propaguje się na wszystkie dzieci. Structured Concurrency to fundamentalna różnica między korutynami a wątkami.
| Scope | Gdzie używany | Anulowanie |
|---|---|---|
| GlobalScope | Tylko do zadań demonicznych | Nie anuluje się automatycznie |
| viewModelScope | Android ViewModel | Przy czyszczeniu ViewModel |
| lifecycleScope | Android Activity/Fragment | Przy zniszczeniu lifecycle |
| coroutineScope | Wewnątrz suspend function | Przy anulowaniu rodzicielskiej job |
Zwykły Job przy błędzie jednej korutyny potomnej anuluje wszystkie siblings. SupervisorJob — wyjątek: awaria jednej korutyny potomnej nie wpływa na pozostałe. Jest to ważne, gdy kilka niezależnych zadań jest wykonywanych równolegle i jedno z nich może upaść bez konieczności anulowania pozostałych.
Dispatchers określają, na których wątkach wykonywane są korutyny. Dispatchers.Main — główny wątek UI androida. Dispatchers.IO — pula do operacji blokujących (sieć, dysk). Dispatchers.Default — do zadań CPU-intensywnych. Dispatchers.Unconfined — startuje w bieżącym wątku, ale nie gwarantuje pozostania na nim. Wybór odpowiedniego Dispatcher jest krytyczny dla wydajności: zadanie IO na Default zablokuje pulę dla obliczeń, a zadanie CPU na IO stworzy nadmiarowe wątki.
withContext — funkcja do przełączania Dispatcher wewnątrz korutyny. Na przykład suspend function wykonująca parsowanie JSON może przełączyć się na Dispatchers.Default podczas obliczeń i wrócić na Dispatchers.Main do aktualizacji UI. withContext to najczęściej używany budowniczy w programowaniu Android.
launch — uruchamia korutynę, zwraca Job, nie zwraca wyniku (fire-and-forget). async — uruchamia korutynę, zwraca Deferred, z którego można pobrać wynik przez await. runBlocking — blokuje bieżący wątek do wykonania korutyny (tylko do testów i funkcji main). Wybór budowniczego zależy od scenariusza: launch nadaje się do zdarzeń i aktualizacji, async — do zadań z wynikiem, runBlocking — tylko do testów lub punktów wejścia.
Rozważmy trzy praktyczne scenariusze: podstawowa korutyna z launch, wywołanie równoległe z async i obsługa błędów z SupervisorJob.
viewModelScope.launch uruchamia korutynę w kontekście ViewModel. Przy czyszczeniu ViewModel korutyna jest automatycznie anulowana.
class ProfileViewModel : ViewModel() {
fun loadUser() {
viewModelScope.launch(Dispatchers.IO) {
val user = api.fetchUser()
withContext(Dispatchers.Main) {
showUser(user)
}
}
}
}
coroutineScope z async uruchamia trzy zapytania równolegle. Wyniki są zbierane przez .await(). Jeśli któreś zapytanie upadnie, wszystkie są anulowane.
suspend fun loadDashboard(): Dashboard = coroutineScope {
val user = async { api.fetchUser() }
val posts = async { api.fetchPosts() }
val stats = async { api.fetchStats() }
Dashboard(user.await(), posts.await(), stats.await())
}
SupervisorJob pozwala każdej korutynie zakończyć się niezależnie. Błąd w jednym zapytaniu nie anuluje pozostałych.
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
scope.launch {
try { api.fetchUsers() } catch (e: Exception) { log(e) }
}
scope.launch {
try { api.fetchPosts() } catch (e: Exception) { log(e) }
}
Wątki (Thread) — prymityw systemu operacyjnego. Każdy wątek ma własny stos (~1 MB) i wymaga wywołania systemowego do utworzenia i przełączenia. Korutyny — prymityw języka, nieprzypisany do systemu operacyjnego. Używają Continuation do zapisu stanu i przełączają się na poziomie runtime bez wywołań systemowych.
Według Google (2025), użycie korutyn zamiast wątków zmniejsza zużycie pamięci na zadania w tle w aplikacjach Android o 90–95%. Wszystkie nowoczesne biblioteki Android (Retrofit, Room, WorkManager) mają wbudowaną obsługę korutyn przez suspend functions. Ktor (kliencki framework HTTP JetBrains) również jest w pełni zbudowany na korutynach, dostarczając suspend-funkcje dla każdego zapytania bez API callback. Room obsługuje korutyny przez suspend-funkcje w DAO, co pozwala wykonywać zapytania do bazy danych bez blokowania głównego wątku.
Wątki pozostają niezbędne dla kodu natywnego przez JNI, blokujących wywołań CPU-intensive bez ograniczenia czasowego (renderowanie wideo, symulacje) oraz przy integracji z bibliotekami C. Do wszystkiego innego — korutyny.
Często zadawane pytania
Korutyna to wstrzymywalna jednostka pracy, która wykonuje się na istniejącym wątku. Wątek to zasób systemowy z własnym stosem. Korutyny są tysiące razy lżejsze od wątków i nie blokują zasobów podczas wstrzymania.
Dispatchers.IO jest przeznaczony do blokujących operacji I/O (sieć, pliki) i może tworzyć nowe wątki w razie potrzeby. Dispatchers.Default ma stałą pulę (liczba rdzeni procesora) do obliczeń CPU-intensywnych.
Job.cancel() anuluje korutynę i wszystkie jej dzieci. Do sprawdzenia anulowania wewnątrz korutyny użyj ensureActive() — wyrzuca CancellationException, jeśli korutyna jest anulowana.
Tak — przez bibliotekę kotlinx-coroutines-rx3. Dostarcza funkcje awaitSingle, awaitFirst i inne do konwersji Observable/Single na suspend functions i z powrotem przez flowable.
Flow to zimny asynchroniczny strumień danych, odpowiednik RxJava Observable dla korutyn. Flow emituje wartości sekwencyjnie i kończy się wyjątkiem lub sukcesem. Obsługuje map, filter, catch i inne operatory.
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ż