> Какие диспетчеры бывают в Kotlin Coroutines (Kotlin, Android)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: MTS, Совкомбанк

Стек: Kotlin, Android

> Пример ответа

Короткий ответ

В Kotlin Coroutines есть три стандартных диспетчера: Dispatchers.Default для CPU-интенсивных задач, Dispatchers.IO для блокирующих операций ввода-вывода и Dispatchers.Main для UI-потока. Также существует Dispatchers.Unconfined, который не меняет поток после запуска, но он редко используется. На практике часто комбинируют диспетчеры через withContext, например, для переключения между IO и Main. Выбор диспетчера напрямую влияет на производительность и отзывчивость приложения.

Подробное объяснение

Диспетчеры в Kotlin Coroutines определяют, на каком пуле потоков или конкретном потоке будет выполняться корутина. Это ключевой механизм управления конкурентностью, который позволяет изолировать блокирующие операции от UI-потока и оптимально использовать ресурсы.

Основные диспетчеры:

  • Dispatchers.Default - пул потоков, размер которого равен количеству ядер процессора (минимум 2). Используется для CPU-bound задач: парсинг JSON, сортировка, сложные вычисления. По умолчанию используется, если диспетчер не указан явно.
  • Dispatchers.IO - пул потоков, оптимизированный для блокирующих операций: чтение из файла, сетевые запросы, работа с БД. Размер пула может быть больше, чем у Default, и ограничен 64 потоками (на JVM). Фактически IO - это отдельный пул, но он может переиспользовать потоки из Default при нехватке ресурсов.
  • Dispatchers.Main - привязан к главному потоку UI (в Android - к main looper). Обязателен для обновления интерфейса. В Android доступен только при подключении зависимости kotlinx-coroutines-android.
  • Dispatchers.Unconfined - запускает корутину в текущем потоке, но после suspension может продолжить в другом. Не рекомендуется для production-кода, так как теряется предсказуемость.

Важно понимать, что диспетчер - это часть CoroutineContext. При создании корутины через launch или async можно передать диспетчер явно. Также можно создавать собственные диспетчеры через Executor.asCoroutineDispatcher() или newSingleThreadContext(), но это требует ручного управления жизненным циклом.

Механизм работы: при вызове withContext(Dispatchers.IO) корутина приостанавливается, переключается на нужный поток, выполняет блок кода, затем возвращает результат на исходный поток (например, Main). Это не блокирует поток, на котором корутина была запущена.

На практике

В Android-приложениях стандартный паттерн - запуск корутины на Dispatchers.Main и переключение на Dispatchers.IO для операций с сетью или БД. Например, в ViewModel:

viewModelScope.launch {
    val data = withContext(Dispatchers.IO) { repository.fetchData() }
    updateUi(data)
}

Здесь viewModelScope автоматически использует Dispatchers.Main, а блок внутри withContext выполняется на IO-пуле. После завершения корутина возвращается на Main для обновления UI.

Для CPU-интенсивных задач, таких как обработка изображений или сложные вычисления, используют Dispatchers.Default. Важно не использовать Dispatchers.IO для CPU-bound задач, так как это может привести к деградации производительности из-за частого переключения контекста.

Также стоит учитывать, что Dispatchers.IO имеет ограничение на количество одновременных блокирующих операций. Если нужно выполнить много параллельных запросов, лучше использовать limitedParallelism() для создания собственного пула с нужным размером.

Пример кода

class MainViewModel(
    private val repository: Repository
) : ViewModel() {

    fun loadData() {
        viewModelScope.launch {
            // Выполняется на Main
            showLoading()

            val result = withContext(Dispatchers.IO) {
                // Блокирующая операция - сетевой запрос
                repository.fetchFromNetwork()
            }

            // Снова на Main
            hideLoading()
            updateUi(result)
        }
    }

    fun processLargeList(items: List<Item>) {
        viewModelScope.launch(Dispatchers.Default) {
            // CPU-интенсивная операция
            val sorted = items.sortedBy { it.priority }
            withContext(Dispatchers.Main) {
                showResult(sorted)
            }
        }
    }
}

Как отвечать на собеседовании

Начни с перечисления стандартных диспетчеров и их назначения. Затем объясни, как они связаны с CoroutineContext и почему важно выбирать правильный диспетчер. Приведи пример из практики: сетевой запрос на IO, обновление UI на Main. Упомяни withContext как основной инструмент переключения. Если спросят про производительность, расскажи про ограничения пулов и limitedParallelism(). Покажи понимание разницы между CPU-bound и IO-bound задачами. Не углубляйся в реализацию пулов, если не просят - достаточно знать, что Default ограничен ядрами, а IO может расти до 64 потоков.

Что проверяет интервьюер

Интервьюер оценивает:

  • понимание назначения каждого диспетчера и сценариев их применения;
  • умение объяснить, почему нельзя выполнять блокирующие операции на Main;
  • знание механизма переключения через withContext;
  • осознание trade-off между Default и IO;
  • понимание, что диспетчер - часть контекста корутины;
  • способность привести корректный пример кода для Android.

Для senior-уровня важно также показать, что ты знаешь про limitedParallelism() и умеешь настраивать пулы под конкретные задачи, а не просто используешь стандартные диспетчеры.

Типичные ошибки

  • Использование Dispatchers.IO для CPU-интенсивных задач - это замедляет выполнение из-за накладных расходов на переключение.
  • Запуск корутины на Dispatchers.IO и попытка обновить UI без переключения на Main - приводит к CalledFromWrongThreadException.
  • Забывают, что Dispatchers.Main недоступен без зависимости kotlinx-coroutines-android.
  • Использование Dispatchers.Unconfined в production-коде - непредсказуемое поведение после suspension.
  • Создание собственных диспетчеров через newSingleThreadContext() без закрытия - утечка потоков.
  • Неправильная оценка размера пула: слишком много параллельных IO-операций может исчерпать пул и заблокировать другие задачи.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью