Dispatchers — kluczowe pojęcia, typy dyspozytorów i jak działają

Autor: IT Sectr Opublikowano: 2026-06-22 Czas czytania: 9 min

Dispatchers w Kotlin Coroutines — komponenty CoroutineContext, określające wątki do wykonywania korutyn: Main (wątek UI), IO (sieć i dysk), Default (zadania CPU-intensywne) i Unconfined (bieżący wątek). Każdy dyspozytor zarządza wyspecjalizowaną pulą wątków, zoptymalizowaną pod konkretny typ pracy. Według przewodnika JetBrains, 2024, wybór odpowiedniego dyspozytora jest krytyczny dla wydajności i stabilności aplikacji.

Najważniejsze

  • Dispatchers — predefiniowane dyspozytory korutyn, zarządzające distribuowaniem zadań po wątkach
  • Dispatchers.Main — wątek UI Androida, do aktualizacji interfejsu i pracy z View
  • Dispatchers.IO — pula dla zapytań sieciowych, odczytu/zapisu plików i baz danych (do 64 wątków)
  • Dispatchers.Default — pula dla zadań CPU-intensywnych według liczby rdzeni procesora
  • Dispatchers.Unconfined — dziedziczenie bieżącego wątku, bez przełączania

Czym są Dispatchers?

Dispatchers — to implementacje interfejsu CoroutineDispatcher, które są elementami CoroutineContext. Określają one, na którym wątku lub puli wątków będzie wykonywana korutyna. Podczas tworzenia korutyny przez launch lub async dyspozytor może być przekazany jako pierwszy parametr: launch(Dispatchers.IO) { ... }. Jeśli dyspozytor nie jest określony, dziedziczy się z zewnętrznego CoroutineScope.

Kotlin dostarcza cztery wbudowane dyspozytory: Main, IO, Default, Unconfined. Każdy dyspozytor używa własnej puli wątków, zoptymalizowanej pod konkretny typ operacji. Prawidłowy wybór dyspozytora decyduje o wydajności aplikacji: błąd wyboru prowadzi do opóźnień interfejsu, marnowania rdzeni CPU lub nieefektywnego zużycia wątków.

DyspozytorPula wątkówMaks. wątkówZastosowanie
Dispatchers.MainJeden (UI)1Aktualizacja UI, LiveData, View
Dispatchers.IOPula IO64 (limitedParallelism)Sieć, pliki, bazy danych
Dispatchers.DefaultPula CPUN rdzeniSortowanie, parsowanie, obliczenia
Dispatchers.UnconfinedBieżący wątekN/DOperacje pośrednie, testy

Dispatchers.Main: wątek UI

Dispatchers.Main — dyspozytor wykonujący korutyny na głównym wątku Androida. Jest przeznaczony do operacji związanych z interfejsem: aktualizacja TextView, wywołanie notifyDataSetChanged, praca z LiveData i StateFlow. W Androidzie ten dyspozytor jest zaimplementowany przez Handler (Looper.getMainLooper()).

kotlin
// Prawidłowe przełączenie na Main dla aktualizacji UI
viewModelScope.launch(Dispatchers.IO) {
    val data = repository.fetchData()
    withContext(Dispatchers.Main) {
        _uiState.value = data
    }
}

Jeśli korutyna jest już na Main-dyspozytorze, dodatkowy withContext(Dispatchers.Main) nie tworzy narzutu — dyspozytor sprawdza bieżący wątek i pomija przełączenie. withContext jest preferowanym sposobem przełączania między dyspozytorami.

Dispatchers.IO: sieć i operacje dyskowe

Dispatchers.IO — dyspozytor zoptymalizowany do operacji wejścia-wyjścia: zapytania HTTP (Ktor, OkHttp), odczyt i zapis plików, praca z Room lub SQLDelight. Używa puli 64 wątków domyślnie, skalowalnej pod obciążeniem. Każde nowe zapytanie IO może utworzyć dodatkowy wątek do osiągnięcia limitu.

Ograniczenie równoległości

Do kontroli liczby jednoczesnych operacji IO używaj limitedParallelism(). Ta funkcja tworzy nowy dyspozytor z ograniczeniem liczby równoległych wątków, zapobiegając wyczerpaniu puli przy masowych operacjach.

kotlin
val limitedIo = Dispatchers.IO.limitedParallelism(4)

// Załaduj 100 plików z limitem 4 równoczesnych operacji
coroutineScope {
    val files = (1..100).map { index ->
        async(limitedIo) {
            downloadFile("file_$index")
        }
    }
    files.awaitAll()
}

Używaj dyspozytora IO dla wszystkich operacji, gdzie korutyna spędza czas na oczekiwaniu (I/O-bound). Zadania CPU-intensywne na dyspozytorze IO są nieefektywne — zajmują wątki przeznaczone do wejścia-wyjścia, obniżając przepustowość systemu.

Dispatchers.Default: zadania CPU-intensywne

Dispatchers.Default — dyspozytor do operacji obliczeniowych obciążających procesor: sortowanie, filtracja, parsowanie JSON (Moshi, Kotlinx Serialization), przetwarzanie obrazów, obliczenia. Rozmiar puli równa się liczbie rdzeni procesora (ale nie mniej niż 2). Zapewnia to maksymalne obciążenie CPU bez przełączania kontekstu.

kotlin
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
    return withContext(Dispatchers.Default) {
        input
            .parallelStream()
            .map { transform(it) }
            .toList()
    }
}

Nie używaj Dispatchers.Default do operacji IO — to zablokuje wątki puli CPU, które mogłyby przetwarzać zadania obliczeniowe. Podział na IO i Default pozwala optymalnie wykorzystać zasoby systemowe: wątki IO czekają na wejście-wyjście, wątki CPU są stale obciążone obliczeniami.

Dispatchers.Unconfined: bieżący wątek

Dispatchers.Unconfined — specjalny dyspozytor, który nie przypisuje korutyny do żadnej puli. Korutyna rozpoczyna wykonanie w tym wątku, w którym została wywołana launch/async, a po zawieszeniu wznawia się w wątku, który wywołał resume. To zachowanie jest odpowiednie dla operacji pośrednich, niewymagających stałego kontekstu.

kotlin
fun main() = runBlocking {
    launch(Dispatchers.Unconfined) {
        println("Przed opóźnieniem: ${Thread.currentThread().getName()}")
        delay(500L)
        println("Po opóźnieniu: ${Thread.currentThread().getName()}")
    }
}

W kodzie produkcyjnym Dispatchers.Unconfined jest stosowany rzadko. Główne przypadki: lekkie transformacje przed przekazaniem danych do innego dyspozytora oraz testy. Dla obciążenia produkcyjnego używaj jawnych dyspozytorów — Unconfined jest nieprzewidywalny, ponieważ wątek wykonania zależy od implementacji resume.

Jak wybrać dyspozytora

Wybór dyspozytora zależy od typu zadania: operacje UI → Main, I/O-bound → IO, CPU-bound → Default, pośrednie → dziedziczenie z scope. Dla Androida zaleca się uruchamianie korutyny na tym dyspozytorze, gdzie wykonywana jest główna praca, a przed aktualizacją UI przełączać się na Main przez withContext.

  • Nie uruchamiaj operacji IO na Main-dyspozytorze — to blokuje UI
  • Nie uruchamiaj zadań CPU-intensywnych na IO-dyspozytorze — to nieefektywnie zużywa wątki puli IO
  • Używaj limitedParallelism do kontroli równoległości przy masowych operacjach IO
  • Przełączaj dyspozytor za pomocą withContext, a nie przez tworzenie nowego scope
  • Dziedzicz dyspozytor z scope, jeśli korutyna nie wymaga specyficznej puli

Dla złożonych scenariuszy łącz dyspozytory za pomocą operatora +: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. To tworzy CoroutineContext z określonym dyspozytorem, obsługą błędów i izolowaną hierarchią Job.

Często zadawane pytania

Czym różnią się Dispatchers.IO i Dispatchers.Default?

Dispatchers.IO używa puli do 64 wątków dla operacji I/O-bound (oczekiwanie na wejście-wyjście), a Dispatchers.Default — pulę według liczby rdzeni CPU dla zadań obliczeniowych. Przy braku wątków obie pule mogą dzielić się wątkami między sobą.

Czy można stworzyć własny dyspozytor w Kotlin Coroutines?

Tak, używaj newSingleThreadContext() dla jednowątkowego lub newFixedThreadPoolContext() dla stałej puli. Dla produkcji stosuj limitedParallelism() na bazie istniejących dyspozytorów — to efektywniejsze niż tworzenie nowych pul.

Co się stanie przy wywołaniu Dispatchers.Main w wątku tła?

Jeśli Dispatchers.Main jest niedostępny (np. w teście JUnit lub usłudze tła), wyrzucany jest IllegalStateException. Do testów używaj TestCoroutineDispatcher, do usług tła — Dispatchers.IO lub Default.

Jak ograniczyć liczbę równoległych operacji IO?

Używaj Dispatchers.IO.limitedParallelism(N), gdzie N — maksymalna liczba równoległych wątków. To zapobiega wyczerpaniu puli przy masowych zapytaniach i daje kontrolowaną równoległość.

Kiedy używać Dispatchers.Unconfined?

Dispatchers.Unconfined jest odpowiedni dla operacji pośrednich: lekkie transformacje danych przed przekazaniem do innego dyspozytora, scenariusze testowe. W kodzie produkcyjnym Androida nie jest zalecany z powodu nieokreślonego wątku wykonania po zawieszeniu.

Podsumowanie

  • Dispatchers — komponenty CoroutineContext, określające wątki wykonania korutyn
  • Dispatchers.Main — do operacji UI w Androidzie, jeden główny wątek
  • Dispatchers.IO — do operacji sieciowych i dyskowych, pula do 64 wątków
  • Dispatchers.Default — do obliczeń CPU-intensywnych, pula według liczby rdzeni
  • Dispatchers.Unconfined — bez przypisania do wątku, do operacji pośrednich
  • withContext — główny mechanizm przełączania między dyspozytorami wewnątrz korutyny
  • Wybór niewłaściwego dyspozytora prowadzi do opóźnień UI lub nieefektywnego wykorzystania zasobów

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ż