使用协程并行执行作业

时间:2019-09-19 12:24:57

标签: kotlin kotlin-coroutines

我想使用协程并行执行多个作业。这是我想到的一段代码。
我有2个查询:

  • 如何确保完成回调在调用方线程中发生?

  • 代码变得更像我以前使用的回调模式 普通线程。请提出更改设计的建议,以实现 协程的可读性优势。

class ParallelExecutor {

    suspend fun <OUTPUT> execute(
        jobs: List<suspend () -> OUTPUT>,
        onTimeout: (jobIndex: Int) -> OUTPUT,
        onFailure: (jobIndex: Int, exception: Throwable) -> OUTPUT,
        onCompletion: suspend (jobIndex: Int, result: OUTPUT) -> Unit,
        timeout: Long,
        onFullCompletion: suspend () -> Unit = {},
        invokeDispatcher: CoroutineDispatcher = Dispatchers.Default
    ) {
        withContext(invokeDispatcher) {
            var counter = 0
            val listenJobs = mutableListOf<Deferred<OUTPUT>>()

            jobs.forEachIndexed { index, job ->
                val listenJob = async {

                    try {
                        job()
                    } catch (e: Exception) {
                        onFailure(index, e)
                    }
                }
                listenJobs.add(listenJob)
            }

            listenJobs.forEachIndexed { index, job ->
                launch {
                    val output = try {
                        withTimeout(timeout) {
                            job.await()
                        }
                    } catch (e: TimeoutCancellationException) {
                        onTimeout(index)
                    }
                    onCompletion(index, output)
                    if (++counter == listenJobs.size) {
                        onFullCompletion()
                    }
                }
            }
        }
    }
}

2 个答案:

答案 0 :(得分:3)

在我看来,您可以简化代码很多。您不需要两步成语,它首先启动所有async作业,然后启动更多作业以等待它们。您可以只launch个作业并委托给同一块内的回调。这样,回调将自然在调用方的调度程序上被调用,并且只有作业本身可以在invokeDispatcher的更改后的上下文中被调用。

onFullCompletion看起来像一段属于调用方的代码,位于execute调用之下。由于execute不会引发任何异常,因此您不需要任何try-finally即可获取它。

suspend fun <OUTPUT> execute(
    jobs: List<suspend () -> OUTPUT>,
    onTimeout: (jobIndex: Int) -> OUTPUT,
    onFailure: (jobIndex: Int, exception: Throwable) -> OUTPUT,
    onCompletion: suspend (jobIndex: Int, result: OUTPUT) -> Unit,
    timeout: Long,
    invokeDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
    coroutineScope {
        jobs.mapIndexed { index, job ->
            launch {
                val output = try {
                    withTimeout(timeout) {
                        withContext(invokeDispatcher) {
                            job()
                        }
                    }
                } catch (e: TimeoutCancellationException) {
                    onTimeout(index)
                } catch (e: Exception) {
                    onFailure(index, e)
                }
                onCompletion(index, output)
            }
        }
    }
}

答案 1 :(得分:1)

做了一些改进,可以回答您的问题。


class ParallelExecutor {

    suspend fun <OUTPUT> execute(
        jobs: List<suspend () -> OUTPUT>,
        onTimeout: (jobIndex: Int) -> OUTPUT,
        onFailure: (jobIndex: Int, exception: Throwable) -> OUTPUT,
        onCompletion: suspend (jobIndex: Int, result: OUTPUT) -> Unit,
        timeout: Long,
        invokeDispatcher: CoroutineDispatcher = Dispatchers.Default
    ) {
        supervisorScope {
            val listenJobs = jobs.map { job ->
                async(invokeDispatcher) {
                    withTimeout(timeout) {
                        job()
                    }
                }
            }

            listenJobs.forEachIndexed { index, job ->
                launch {
                    val output = try {
                        job.await()
                    } catch (e: TimeoutCancellationException) {
                        onTimeout(index)
                    } catch (e: Exception) {
                        onFailure(index, e)
                    }
                    onCompletion(index, output)
                }
            }
        }
    }
}

  • 现在达到超时时,作业将被取消。
  • 完成回调现在在调用方的调度程序中被调用。
  • 确定何时呼叫onFullCompletion时的竞赛条件已解决。
  • 删除了一些您实际上不需要的方法。

如果您觉得这更像是回调模式,则根本不应该使用回调。协程的设计使您可以在使用现场以最少的样板编写此类代码,因此不需要这样的功能并且看起来很怪异(IMHO)。