Dispatchers — ключові поняття, типи диспетчерів та як вони працюють

Автор: IT Sectr Опубліковано: 2026-06-22 Час читання: 9 хв

Dispatchers у Kotlin Coroutines — компоненти CoroutineContext, що визначають потоки для виконання корутин: Main (UI-потік), IO (мережа та диск), Default (CPU-інтенсивні завдання) та Unconfined (поточний потік). Кожен диспетчер керує спеціалізованим пулом потоків, оптимізованим під конкретний тип роботи. За даними посібника JetBrains, 2024, вибір правильного диспетчера критичний для продуктивності та стабільності застосунку.

Головне

  • Dispatchers — попередньо визначені диспетчери корутин, що керують розподілом завдань по потоках
  • Dispatchers.Main — UI-потік Android, для оновлення інтерфейсу та роботи з View
  • Dispatchers.IO — пул для мережевих запитів, читання/запису файлів і БД (до 64 потоків)
  • Dispatchers.Default — пул для CPU-інтенсивних завдань за кількістю ядер процесора
  • Dispatchers.Unconfined — успадкування поточного потоку, без перемикання

Що таке Dispatchers?

Dispatchers — це реалізації інтерфейсу CoroutineDispatcher, які є елементами CoroutineContext. Вони визначають, на якому потоці чи пулі потоків виконуватиметься корутина. При створенні корутини через launch або async диспетчер може бути переданий як перший параметр: launch(Dispatchers.IO) { ... }. Якщо диспетчер не вказано, він успадковується від зовнішнього CoroutineScope.

Kotlin надає чотири вбудованих диспетчери: Main, IO, Default, Unconfined. Кожен диспетчер використовує власний пул потоків, оптимізований під конкретний тип операцій. Правильний вибір диспетчера визначає продуктивність застосунку: помилка вибору призводить до лагів інтерфейсу, простоювання ядер CPU або неефективного витрачання потоків.

ДиспетчерПули потоківМакс. потоківЗастосування
Dispatchers.MainОдин (UI)1Оновлення UI, LiveData, View
Dispatchers.IOIO-пул64 (limitedParallelism)Мережа, файли, БД
Dispatchers.DefaultCPU-пулN ядерСортування, парсинг, обчислення
Dispatchers.UnconfinedПоточний потікN/AПроміжні операції, тести

Dispatchers.Main: UI-потік

Dispatchers.Main — диспетчер, що виконує корутини на головному потоці Android. Він призначений для операцій, пов'язаних з інтерфейсом: оновлення TextView, виклик notifyDataSetChanged, робота з LiveData та StateFlow. В Android цей диспетчер реалізований через Handler (Looper.getMainLooper()).

kotlin
// Correct switch to Main for UI updates
viewModelScope.launch(Dispatchers.IO) {
    val data = repository.fetchData()
    withContext(Dispatchers.Main) {
        _uiState.value = data
    }
}

Якщо корутина вже на Main-диспетчері, додатковий withContext(Dispatchers.Main) не створює накладних витрат — диспетчер перевіряє поточний потік і пропускає перемикання. withContext є кращим способом перемикання між диспетчерами.

Dispatchers.IO: мережа та дискові операції

Dispatchers.IO — диспетчер, оптимізований для операцій введення-виведення: HTTP-запити (Ktor, OkHttp), читання та запис файлів, робота з Room або SQLDelight. Він використовує пул із 64 потоків за замовчуванням, масштабований під навантаження. Кожен новий IO-запит може створити додатковий потік до досягнення ліміту.

Обмеження паралелізму

Для контролю кількості одночасних IO-операцій використовуйте limitedParallelism(). Ця функція створює новий диспетчер з обмеженням на кількість паралельних потоків, запобігаючи виснаженню пулу при масових операціях.

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

// Load 100 files with limit of 4 concurrent operations
coroutineScope {
    val files = (1..100).map { index ->
        async(limitedIo) {
            downloadFile("file_$index")
        }
    }
    files.awaitAll()
}

Використовуйте IO-диспетчер для всіх операцій, де корутина проводить час в очікуванні (I/O-bound). CPU-інтенсивні завдання на IO-диспетчері неефективні — вони займають потоки, призначені для введення-виведення, знижуючи пропускну здатність системи.

Dispatchers.Default: CPU-інтенсивні завдання

Dispatchers.Default — диспетчер для обчислювальних операцій, що завантажують процесор: сортування, фільтрація, парсинг JSON (Moshi, Kotlinx Serialization), обробка зображень, обчислення. Розмір пулу дорівнює кількості ядер процесора (але не менше 2). Це забезпечує максимальне завантаження CPU без перемикання контексту.

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

Не використовуйте Dispatchers.Default для IO-операцій — це заблокує потоки CPU-пулу, які могли б обробляти обчислювальні завдання. Розділення на IO та Default дозволяє оптимально утилізувати системні ресурси: IO-потоки чекають введення-виведення, CPU-потоки постійно завантажені обчисленнями.

Dispatchers.Unconfined: поточний потік

Dispatchers.Unconfined — спеціальний диспетчер, який не прив'язує корутину до жодного пулу. Корутина починає виконання в тому потоці, де була викликана launch/async, а після призупинення відновлюється в потоці, який викликав resume. Ця поведінка підходить для проміжних операцій, що не потребують фіксованого контексту.

kotlin
fun main() = runBlocking {
    launch(Dispatchers.Unconfined) {
        println("Before delay: ${Thread.currentThread().getName()}")
        delay(500L)
        println("After delay: ${Thread.currentThread().getName()}")
    }
}

У production-коді Dispatchers.Unconfined застосовується рідко. Основні випадки: легкі трансформації перед передачею даних в інший диспетчер і тести. Для production-навантаження використовуйте явні диспетчери — Unconfined непередбачуваний, оскільки потік виконання залежить від реалізації resume.

Як вибрати диспетчер

Вибір диспетчера визначається типом завдання: UI-операції → Main, I/O-bound → IO, CPU-bound → Default, проміжні → успадкування від scope. Для Android рекомендується корутину запускати на тому диспетчері, де виконується основна робота, а перед оновленням UI перемикатися на Main через withContext.

  • Не запускайте IO-операції на Main-диспетчері — це блокує UI
  • Не запускайте CPU-інтенсивні завдання на IO-диспетчері — це неефективно витрачає потоки IO-пулу
  • Використовуйте limitedParallelism для контролю паралелізму при масових IO-операціях
  • Перемикайте диспетчер за допомогою withContext, а не створенням нового scope
  • Успадковуйте диспетчер від scope, якщо корутина не потребує специфічного пулу

Для складних сценаріїв комбінуйте диспетчери за допомогою + оператора: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. Це створює CoroutineContext із заданим диспетчером, обробкою помилок та ізольованою ієрархією Job.

Часті запитання

Чим відрізняються Dispatchers.IO та Dispatchers.Default?

Dispatchers.IO використовує пул до 64 потоків для I/O-bound операцій (очікування введення-виведення), а Dispatchers.Default — пул за кількістю CPU-ядер для обчислювальних завдань. При нестачі потоків обидва пули можуть ділитися потоками один з одним.

Чи можна створити свій диспетчер у Kotlin Coroutines?

Так, використовуйте newSingleThreadContext() для однопотокового або newFixedThreadPoolContext() для фіксованого пулу. Для продакшену застосовуйте limitedParallelism() на основі існуючих диспетчерів — це ефективніше, ніж створення нових пулів.

Що станеться при виклику Dispatchers.Main у фоновому потоці?

Якщо Dispatchers.Main недоступний (наприклад, у JUnit-тесті або фоновому сервісі), викидається IllegalStateException. Для тестів використовуйте TestCoroutineDispatcher, для фонових сервісів — Dispatchers.IO або Default.

Як обмежити кількість паралельних IO-операцій?

Використовуйте Dispatchers.IO.limitedParallelism(N), де N — максимальна кількість паралельних потоків. Це запобігає виснаженню пулу при масових запитах і дає контрольований паралелізм.

Коли використовувати Dispatchers.Unconfined?

Dispatchers.Unconfined підходить для проміжних операцій: легкі трансформації даних перед передачею в інший диспетчер, тестові сценарії. У production-коді Android не рекомендується через невизначений потік виконання після призупинення.

Підсумки

  • Dispatchers — компоненти CoroutineContext, що визначають потоки виконання корутин
  • Dispatchers.Main — для UI-операцій Android, один головний потік
  • Dispatchers.IO — для мережевих та дискових операцій, пул до 64 потоків
  • Dispatchers.Default — для CPU-інтенсивних обчислень, пул за кількістю ядер
  • Dispatchers.Unconfined — без прив'язки до потоку, для проміжних операцій
  • withContext — основний механізм перемикання між диспетчерами всередині корутини
  • Вибір неправильного диспетчера веде до лагів UI або неефективного використання ресурсів

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також