我有一个 flow
(MutableSharedFlow
,如果它相关),并且我有潜在的昂贵操作,我希望异步执行,同时仍保持顺序。我使用 CompletableFuture
实现了我想要的:
private val threadPoolSize = 5
private val threadPool = Executors.newFixedThreadPool(threadPoolSize)
fun process(flow: Flow<String>) = flow
.map { CompletableFuture.supplyAsync({ expensiveHandle(it) }, threadPool) }
.buffer(threadPoolSize)
.map { it.get() } // block current thread
.flowOn(threadPool.asCoroutineDispatcher())
由于卸载到线程池、固定大小 buffer
和 线程阻塞 CompletableFuture#get
的组合,此代码符合我的预期 - 最多 threadPoolSize
个事件并行处理,并按照接收顺序发送到流中。
当我用 CompletableFuture#get
中的扩展函数 CompletableFuture#await
替换 kotlinx.coroutines.future
并使用 flow
或 async
而不是 CompletableFuture#supplyAsync
时,消息不是并行处理时间更长:
fun process(flow: Flow<String>) = flow
.map {
runBlocking {
future { expensiveHandle(it) } // same behaviour with async {...}
}
}
.buffer(threadPoolSize)
.map { it.await() }
.flowOn(threadPool.asCoroutineDispatcher())
我可以使用协程/挂起函数做等效的代码吗?
答案 0 :(得分:1)
async
和 future
是 CoroutineScope
的扩展函数。所以,你需要一些
CoroutineScope
给他们打电话。
runBlocking
给出了一些 CoroutineScope
,但它是一个阻塞调用,因此它在 suspend
函数 is prohibited 中的用法。
您可以使用 GlobalScope.async
,但它也是 not recommended 并且执行将由 Dispatchers.Default
分派,而不是由 threadPool.asCoroutineDispatcher()
分派,如原始示例中的 CompletableFuture
.
coroutineScope
和 withContext
函数将提供 CoroutineScope
,它从外部作用域继承其 coroutineContext
,因此流处理将暂停并立即执行 expensiveHandle(it)
协程。
您需要使用工厂函数创建 CoroutineScope
,以便协程上下文不会混合:
fun process(flow: Flow<String>, threadPool: ThreadPoolExecutor): Flow<String> {
val dispatcher = threadPool.asCoroutineDispatcher()
return flow
.map { CoroutineScope(dispatcher).async { expensiveHandle(it) } }
.buffer(threadPool.poolSize)
.map { it.await() }
.flowOn(dispatcher)
}
答案 1 :(得分:0)
不要映射作为参数传递的流,而是尝试返回使用 callbackFlow
构建器构建的新流并在其中收集流,这样您就可以启动多个协程来调用 expensiveHandle(it)
并发送各自的结果尽快。
fun process(flow: Flow<String>) = callbackFlow {
flow.collect {
launch {
send(expensiveHandle(it))
}
}
}.flowOn(threadPool.asCoroutineDispatcher())
答案 2 :(得分:0)
所以问题不在于 future
本身,而是周围的 runBlocking
。将自定义 CoroutineScope
与线程池一起用作底层调度程序时,代码按预期工作(注意将 get
更改为 await
,并且我使用了 async
而不是future
因为它在核心协程库中):
private val threadPoolSize = 5
private val threadPool = Executors.newFixedThreadPool(threadPoolSize)
private val dispatcher = threadPool.asCoroutineDispatcher()
private val scope = CoroutineScope(dispatcher)
fun process(flow: Flow<String>) = flow
.map { scope.async(expensiveHandle(it)) }
.buffer(threadPoolSize)
.map { it.await() }
.flowOn(dispatcher)