Если по каким-то причинам нет возможности заменить блокирующий код неблокирующим аналогом (например, драйвер БД, для которого асинхронной версии просто не успели ещё написать), то можно использовать такое решение:
1. Создаёте отдельный тредпул
2. Все вызовы блокирующего кода оборачиваете в блок
suspendCoroutine { continuation ->
//
}
3. Внутри этого блока создаёте
Runnable
, который и будет выполнять ваш блокирующий код. После выполнения блокирующего кода вызываете либо
continuation.resume(result)
, либо
continuation.resumeWithException(e)
4. Скармливаете полученный
Runnable
тредпулу.
Это приведёт к тому, что оригинальная корутина засаспендится, блокирующий код будет выполняться в отдельных потоках, не мешая* остальным корутинам, и когда блокирующий код завершится, корутина будет разбужена с готовым результатом.
* - В котлине по умолчанию используется диспатчер
Dispatcher.Default
, количество потоков в котором равно количеству ядер CPU (но не меньше двух), поэтому добавление отдельного тредпула под блокирующие задачи приведёт к увеличению количества тредов, что в экстремальных случаях может привести к общей деградации производительности. Но в если у вас не высоконагруженное приложение, то этим можно пренебречь. Правда не уверен как с этим обстоят дела на мобильных платформах, возможно там количество тредов более критично.
Из очевидных минусов:
- количество потоков больше, чем ядер CPU
- количество одновременно выполняемых блокирующих методов = количеству потоков в этом тредпуле
- следите за тем, что бы этот тредпул был ограничен сверху
.
Насколько я помню, еще ~1.5 года назад примерно такая схема применялась в котлиновских обёртках над спринговыми драйверами то ли к монго, то ли к редису (там было 3 версии драйвера - блокирующая, реактивная, и блокирующая, но обёрнутая в отдельный тредпул, как я описал). Как сейчас - не знаю.