Kotlin 流顺序异步处理

时间:2021-03-30 09:50:55

标签: kotlin asynchronous future kotlin-coroutines kotlin-flow

我有一个 flowMutableSharedFlow,如果它相关),并且我有潜在的昂贵操作,我希望异步执行,同时仍保持顺序。我使用 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 并使用 flowasync 而不是 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())

我可以使用协程/挂起函数做等效的代码吗?

3 个答案:

答案 0 :(得分:1)

asyncfutureCoroutineScope 的扩展函数。所以,你需要一些 CoroutineScope 给他们打电话。

runBlocking 给出了一些 CoroutineScope,但它是一个阻塞调用,因此它在 suspend 函数 is prohibited 中的用法。

您可以使用 GlobalScope.async,但它也是 not recommended 并且执行将由 Dispatchers.Default 分派,而不是由 threadPool.asCoroutineDispatcher() 分派,如原始示例中的 CompletableFuture .

coroutineScopewithContext 函数将提供 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)