Kotlin: Какое влияние на производительность оказывает преобразование «нормальной» функции в блокирующую функцию приостановки?

У меня есть функция, которая выглядит так:

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) } }

Итак, у меня есть два вопроса:

  1. Есть ли какие-либо соображения по поводу производительности при использовании «нормальной» функции, передаче ее в качестве параметра suspend, а затем блокировке до тех пор, пока не будет получен результат?
    • Основываясь на том, что я прочитал, похоже, что исходный поток продолжает «выполнять работу» внутри блока приостановки, пока не достигнет первой точки приостановки. Затем продолжение помещается в очередь ожидания, и исходный поток свободен для выполнения другой работы.
    • Однако в этом случае нет никакой «настоящей» точки приостановки, потому что фактическая функция — это просто (T) -> R, хотя я не знаю, может ли это сказать компилятор.
    • Я беспокоюсь, что эта установка на самом деле использует другой поток из пула, который просто уведомляет мой первый поток о пробуждении...
  2. Есть ли лучший способ, чтобы набор функций с приостановкой и без приостановки использовал один и тот же код?
Асинхронная передача данных с помощью sendBeacon в JavaScript
Асинхронная передача данных с помощью sendBeacon в JavaScript
В современных веб-приложениях отправка данных из JavaScript на стороне клиента на сервер является распространенной задачей. Одним из популярных...
1
0
528
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ваш подход правильный, 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://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html

Также далее читайте: https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/basics.md#bridging-blocking-and-non-blocking-worlds

И несколько интересных видео Романа Елизарова:

https://thewikihow.com/video__hfBv0a09Jc

https://thewikihow.com/video_a3agLJQ6vt8

Ответ принят как подходящий

Вы столкнулись с печально известной проблемой "цветная функция". Эти два мира действительно разделены, и, хотя вы можете добавить поверхностный слой, который их объединяет, вы не можете получить его при нулевой стоимости производительности. Это настолько фундаментально, что даже если предположить, что ваш блок suspend на самом деле никогда не приостанавливается, а уровень оболочки использует это предположение и даже не использует runBlocking, вы заплатите еще цену за «готовность к приостановке». Однако цена невелика: это означает создание небольшого объекта для каждого вызова suspend fun, который содержит данные, которые обычно находятся в собственном стеке вызовов потока. В вашем случае приостанавливается только внешний блок, так что это всего лишь один такой объект.

runBlocking запускает сопрограмму в потоке, в котором вы ее вызвали, и она завершится синхронно в том же потоке, если только не приостановит себя. Поэтому в вашем случае, когда у вас есть синхронный код в блоке suspend, не возникнет дополнительного снижения производительности из-за координации потоков.

Если сопрограмма приостанавливает себя, то должен быть какой-то внешний рабочий поток, который будет реагировать на событие, позволяющее возобновить сопрограмму, и должна быть некоторая координация между этим потоком и вашим исходным runBlocking потоком. Это фундаментальный механизм, который существует как с сопрограммами, так и без них.

Другие вопросы по теме