У меня есть функция, которая выглядит так:
fun <R> map(block: (T) -> R): Result<R> { ... }
и я хотел бы сделать приостанавливающую версию:
suspend fun <R> mapAsync(block: suspend (T) -> R): Result<R> { ... }
Логика в обоих телах идентична, но один приостанавливается, а другой нет.
Я не хочу иметь эту дублированную логику. Единственный способ, который я нашел для этого, - это вызвать функцию map
для функции mapAsync
, а затем обернуть результат в runBlocking
:
fun <R> map(block: (T) -> R): Result<R> =
runBlocking { mapAsync { block(it) } }
Итак, у меня есть два вопроса:
suspend
, а затем блокировке до тех пор, пока не будет получен результат?
(T) -> R
, хотя я не знаю, может ли это сказать компилятор.Ваш подход правильный, runBlocking
был специально разработан, чтобы служить связующим звеном между блокирующими и неблокирующими операциями. Из документации:
Runs new coroutine and blocks current thread interruptibly until its completion. This function should not be used from coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.
Также далее читайте: https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/basics.md#bridging-blocking-and-non-blocking-worlds
И несколько интересных видео Романа Елизарова:
Вы столкнулись с печально известной проблемой "цветная функция". Эти два мира действительно разделены, и, хотя вы можете добавить поверхностный слой, который их объединяет, вы не можете получить его при нулевой стоимости производительности. Это настолько фундаментально, что даже если предположить, что ваш блок suspend
на самом деле никогда не приостанавливается, а уровень оболочки использует это предположение и даже не использует runBlocking
, вы заплатите еще цену за «готовность к приостановке». Однако цена невелика: это означает создание небольшого объекта для каждого вызова suspend fun
, который содержит данные, которые обычно находятся в собственном стеке вызовов потока. В вашем случае приостанавливается только внешний блок, так что это всего лишь один такой объект.
runBlocking
запускает сопрограмму в потоке, в котором вы ее вызвали, и она завершится синхронно в том же потоке, если только не приостановит себя. Поэтому в вашем случае, когда у вас есть синхронный код в блоке suspend
, не возникнет дополнительного снижения производительности из-за координации потоков.
Если сопрограмма приостанавливает себя, то должен быть какой-то внешний рабочий поток, который будет реагировать на событие, позволяющее возобновить сопрограмму, и должна быть некоторая координация между этим потоком и вашим исходным runBlocking
потоком. Это фундаментальный механизм, который существует как с сопрограммами, так и без них.