Coroutines — kluczowe pojęcia, Job i Dispatchers w Kotlin

Autor: IT Sectr Opublikowano: 2026-03-16 Czas czytania: 8 min

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 — lekkie wątki Kotlin do kodu asynchronicznego bez blokowania
  • suspend function — funkcja zdolna do wstrzymania i wznowienia bez blokowania wątku
  • Dispatcher określa pulę wątków do wykonania korutyny
  • Job — deskryptor korutyny z obsługą anulowania i śledzenia stanu
  • CoroutineScope zarządza cyklem życia korutyn i ich anulowaniem przy zakończeniu

Czym są korutyny Kotlin

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.

Jak korutyny działają pod maską

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 functions: wstrzymywanie i wznawianie

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.

  • Wstrzymanie — korutyna zwalnia wątek, nie blokując go
  • Wznowienie — korutyna kontynuuje od miejsca, w którym się wstrzymała
  • Wątek — korutyna może wstrzymać się na wątku A i wznowić na wątku B
  • Wyjątki — obsługiwane przez try/catch tak samo jak w kodzie synchronicznym

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 i Structured Concurrency

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.

ScopeGdzie używanyAnulowanie
GlobalScopeTylko do zadań demonicznychNie anuluje się automatycznie
viewModelScopeAndroid ViewModelPrzy czyszczeniu ViewModel
lifecycleScopeAndroid Activity/FragmentPrzy zniszczeniu lifecycle
coroutineScopeWewnątrz suspend functionPrzy anulowaniu rodzicielskiej job

SupervisorJob do obsługi błędów

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 i budownicze korutyn

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.

Trzej główni budowniczy korutyn

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.

Przykłady kodu z korutynami w Kotlin

Rozważmy trzy praktyczne scenariusze: podstawowa korutyna z launch, wywołanie równoległe z async i obsługa błędów z SupervisorJob.

Uruchamianie korutyny z launch

viewModelScope.launch uruchamia korutynę w kontekście ViewModel. Przy czyszczeniu ViewModel korutyna jest automatycznie anulowana.

kotlin
class ProfileViewModel : ViewModel() {
    fun loadUser() {
        viewModelScope.launch(Dispatchers.IO) {
            val user = api.fetchUser()
            withContext(Dispatchers.Main) {
                showUser(user)
            }
        }
    }
}

Równoległe zapytania z async

coroutineScope z async uruchamia trzy zapytania równolegle. Wyniki są zbierane przez .await(). Jeśli któreś zapytanie upadnie, wszystkie są anulowane.

kotlin
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())
}

Obsługa błędów z SupervisorJob

SupervisorJob pozwala każdej korutynie zakończyć się niezależnie. Błąd w jednym zapytaniu nie anuluje pozostałych.

kotlin
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) }
}

Korutyny vs wątki: porównanie i scenariusze

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.

  • Pamięć — wątek ~1 MB, korutyna ~100 bajtów. Różnica 10 000 razy
  • Tworzenie — wątek ~1 µs syscall, korutyna ~0.01 µs na poziomie JVM
  • Przełączanie — wątek ~0.1 µs (syscall), korutyna ~0.001 µs (continuation)
  • Maksimum — tysiące wątków vs miliony korutyn na urządzeniu
  • Anulowanie — wątku nie można anulować z zewnątrz (deprecated Thread.stop), korutynę można przez Job.cancel()

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.

Kiedy używać wątków zamiast korutyn

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

Czym różni się korutyna od wątku?

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.

Czym jest Dispatchers.IO i czym różni się od Default?

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.

Jak anulować uruchomioną korutynę?

Job.cancel() anuluje korutynę i wszystkie jej dzieci. Do sprawdzenia anulowania wewnątrz korutyny użyj ensureActive() — wyrzuca CancellationException, jeśli korutyna jest anulowana.

Czy można używać korutyn z RxJava?

Tak — przez bibliotekę kotlinx-coroutines-rx3. Dostarcza funkcje awaitSingle, awaitFirst i inne do konwersji Observable/Single na suspend functions i z powrotem przez flowable.

Czym jest Flow w korutynach?

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

  • Coroutines — lekkie wątki Kotlin z wstrzymywaniem bez blokowania przez Continuation Passing Style
  • suspend — słowo kluczowe do oznaczania funkcji wstrzymywalnych
  • Dispatchers zarządzają pulą wątków: Main, IO, Default odpowiednio
  • CoroutineScope wiąże cykl życia korutyn z komponentem (Activity, ViewModel)
  • launch uruchamia korutynę bez wyniku, async/await — z wynikiem
  • Structured Concurrency gwarantuje hierarchiczne anulowanie korutyn potomnych
  • Korutyny vs wątki — korutyny są 10 000 razy lżejsze i są standardem dla Android

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.

Omów projekt

Przeczytaj również