Dispatchers.IOМы привыкли думать, что
Dispatchers.IO - это бездонная бочка. Закинул туда сетевой запрос или чтение файла, и корутины сами всё разрулят.Но у
Dispatchers.IO есть жесткий лимит - 64 потока (или количество ядер процессора, если их больше).Сценарий катастрофы:
Вам нужно скачать 100 картинок или сделать батч-запросы к очень мееееедленному стороннему API. Вы запускаете 100 корутин на
IO.Первые 64 запроса занимают все доступные потоки пула и зависают в ожидании ответа от сервера (I/O block).
В этот момент пользователь нажимает кнопку "Сохранить профиль". Метод сохранения идет в локальную БД (Room/Realm), которая тоже использует
Dispatchers.IO.Результат: Запрос в БД не выполняется. Он встает в очередь и ждет, пока хотя бы одна из 64 картинок скачается и освободит системный поток. Пользователь видит бесконечный лоадер. Приложение кажется "зависшим", хотя ANR нет.
🛠 Как это лечат Джуны?
Создают свой пул:
Executors.newFixedThreadPool(100).asCoroutineDispatcher().Почему это плохо: Вы плодите тяжеловесные системные потоки в обход общего механизма корутин. Это жрет память и ломает шаринг ресурсов.
👑 Как это лечат Сеньоры?
Используют
limitedParallelism. У этого метода есть две суперсилы.Суперсила 1: Расширение лимита
// Да, этот вызов легально расширяет лимит для конкретной задачи!
val ImageDownloadDispatcher = Dispatchers.IO.limitedParallelism(100)
Документация гласит: вызов
limitedParallelism на Dispatchers.IO создает независимый пул, который может превышать глобальный лимит в 64 потока, при этом используя общий elastic thread pool под капотом. Картинки будут качаться в 100 потоков, не блокируя остальные задачи на дефолтном IO.Суперсила 2: Изоляция узких бутылочных горлышек (Bottlenecks)
Допустим, у вашей базы данных (SQLite/Room) пул коннектов равен 4.
Если вы отправите 20 корутин писать в БД на
Dispatchers.IO, 4 будут писать, а 16 - тупо заблокируют потоки IO, ожидая свободного коннекта к БД.Делаем так:
val DbDispatcher = Dispatchers.IO.limitedParallelism(4)
Теперь, если вы запустите 20 корутин на
DbDispatcher, только 4 потока будут заняты. Остальные 16 корутин будут приостановлены (suspended), не блокируя системные потоки. Ваш основной IO диспетчер останется свободным для сети и файлов.Чек-лист:
Выделяйте кастомные диспетчеры через
limitedParallelism для:1. Массовых долгих сетевых операций.
2. Работы с БД (по размеру connection pool).
3. Работы с Legacy SDK, которые не поддерживают асинхронность.
Кто из вас уже сталкивался с Thread Starvation в корутинах? Искали долго? 👇
✍️ @kotlin_lib