2

我想用不同线程的结果更新一个列表。

mainFunction(): List<A> {
  var x: List<A> = listOf<A>()
  val job = ArrayList<Job>()
  val ans = mainScope.async {
            var i = 0
            for (j in (0..5)) {
                job.add(
                    launch {
                        val res = async {
                            func1()
                        }
                        x += res.await()
                    }
                )
            }
         job.joinAll()
        }
  ans.await()
  return x
}
fun func1(): List<A> {
 //Perform some operation to get list abc
 var abc: List<A> = listOf<A>()
 delay(1000)
 return abc
}

列表“x”未正确更新。有时,它会附加“res”..有时它不会。有没有一种线程安全的方法来修改这样的列表?

新实现:

mainFunction(): List<A> {
 var x: List<A> = listOf<A>()
 val ans = mainScope.async {
   List(6) {
     async{
      func1()
     }
   }.awaitAll()
 }
 print(ans)
 for (item in ans) {
  x+= item as List<A> // item is of type Kotlin.Unit
 }
}
4

2 回答 2

4

如果您只想同时执行一些任务并在最后获得所有已完成结果的列表,您可以这样做:

val jobs = (0..5).map { mainScope.async { func1() } }
val results = jobs.awaitAll()
于 2021-10-02T17:10:45.803 回答
3

简短的回答

这是您正在做的一个更简单的版本,它避免了您可能遇到的同步问题:

suspend fun mainFunction(): List<A> {
    return coroutineScope {
        List(6) { async { func1() } }.awaitAll()
    }
}

如果你想解开这个,你可以阅读长答案。我将解释原始代码中并非真正惯用且可以替换的不同内容。

长答案

问题中的代码中有多个非惯用的东西,所以我将尝试解决它们中的每一个。

基于 0 的范围的索引 for 循环

如果您只想多次重复一个操作,使用repeat(6)而不是for (j in 0..5). 它更容易阅读,尤其是当您不需要索引变量时:

suspend fun mainFunction(): List<A> {
    var x: List<A> = listOf<A>()
    val job = ArrayList<Job>()
    val ans = mainScope.async {
        repeat(6) {
            job.add(
                launch {
                    val res = async {
                        func1()
                    }
                    x += res.await()
                }
            )
        }
        job.joinAll()
    }
    ans.await()
    return x
}

使用循环创建列表

如果您想要从该循环中创建一个列表,您还可以使用List(size) { computeElement() }代替repeat(或for),它利用了List 工厂函数

suspend fun mainFunction(): List<A> {
    var x: List<A> = listOf<A>()
    val ans = mainScope.async {
        val jobs = List(6) {
            launch {
                val res = async {
                    func1()
                }
                x += res.await()
            }
        }
        jobs.joinAll()
    }
    ans.await()
    return x
}

额外的异步

无需在此处使用额外的异步包装您的启动,您可以launch直接在 es 上使用您的范围:

suspend fun mainFunction(): List<A> {
    var x: List<A> = listOf<A>()
    val jobs = List(6) {
        mainScope.launch {
            val res = async {
                func1()
            }
            x += res.await()
        }
    }
    jobs.joinAll()
    return x
}

异步 + 立即等待

使用async { someFun() }然后立即await-ing 这个Deferred结果等同于直接调用someFun()(除非您使用不同的范围或上下文,您在这里没有为最内部的逻辑执行此操作)。

所以你可以替换最里面的部分:

val res = async {
    func1()
}
x += res.await()

通过 just x += func1(),它给出:

suspend fun mainFunction(): List<A> {
    var x: List<A> = listOf<A>()
    val jobs = List(6) {
        mainScope.launch {
            x += func1()
        }
    }
    jobs.joinAll()
    return x
}

启动与异步

如果您想要结果,通常使用async而不是launch. 当您使用 时launch,您必须手动将结果存储在某处(这会使您遇到像现在这样的同步问题)。有了async,你得到一个Deferred<T>值,你可以然后await(),当你有一个Deferred没有同步问题的列表时,当你等待它们时。

所以前面代码的总体思路是不好的做法,可能会咬你,因为它需要手动同步。您可以通过以下方式替换它:

suspend fun mainFunction(): List<A> {
    val deferredValues = List(6) {
        mainScope.async {
            func1()
        }
    }
    val x = deferredValues.awaitAll()
    return x
}

或更简单:

suspend fun mainFunction(): List<A> {
    return List(6) {
        mainScope.async {
            func1()
        }
    }.awaitAll()
}

手动连接与 coroutineScope

join()手动作业通常是一种气味。如果你想等待一些协程完成,在一个coroutineScope { ... }block中启动所有这些协程更为惯用,这将暂停直到所有子协程完成。

在这里,我们已经用调用 we 替换了所有 we launch,所以这不再适用,因为我们仍然需要延迟值才能获得结果。但是,由于我们已经在挂起函数中,我们仍然可以使用而不是外部作用域来确保我们不会泄漏任何协程:join()asyncawaitawait()coroutineScopemainScope

suspend fun mainFunction(): List<A> {
    return coroutineScope {
        List(6) { async { func1() } }.awaitAll()
    }
}
于 2021-10-03T11:53:42.477 回答