我想使用协程并行执行多个作业。这是我想到的一段代码。
我有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()
}
}
}
}
}
}
答案 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)。