Kotlin 协程异步等待序列

Kotlin Coroutines Async Await Sequence

能否请您解释一下这两个代码块之间的区别。第一次打印421,第二次打印606,为什么第一次是并行,第二次是顺序?

fun main(args: Array<String>) = runBlocking {

    var time = measureTimeMillis {
        val one = async { one() }
        val two = async { two() }
        val int1 = one.await()
        val int2 = two.await()
        println(int1 + int2)

    }

    println(time)


    time = measureTimeMillis {
        val one = async { one() }.await()
        val two = async { two() }.await()
        println(one + two)

    }

    print(time)
}

suspend fun one(): Int {
    delay(200)
    return 12
}

suspend fun two(): Int {
    delay(400)
    return 23
}

在第一个变体中,两个异步调用都得到一个 Deferred<Int>the documentation of Deferred 很好地显示了延迟对象可能处于的几种状态。从外部看,该状态现在是 newactive 但肯定还没有完成。然而,在你的第二个变体上,第一个异步等待已经需要一个 completed 状态,否则你在那里没有任何价值。但是,在您的 async{one()}.await() 上,第二个 async 尚不清楚。 另请注意,await() 的 return 值现在是 Int 而不是 Deferred,因此协程必须在那时执行。还要检查 the documentation of await().

换句话说:

val one = async { one() }
val two = async { two() }

onetwo 现在都是 Deferred<Int>。 None 已被调用(或可能已被调用)。一旦你调用 one.await() 它可能已经同时启动 onetwo,只是因为它有它的资源(即使你没有在任何地方使用 two.await()你的代码)。

但是在第二个变体中:

val one = async { one() }.await()
val two = async { two() }.await()

即使它为 async {one()} 创建了一个协程,它也必须立即将一个值设置为 one,因为您正在对它调用 await()onetwo的类型都是Int。因此,一旦命中这些行中的第一行,就需要立即执行异步代码。到那时,没有人知道在我们等待第一个异步调用的值时必须执行另一个异步调用。如果第一个没有 await,协程将再次并行执行,例如:

val one = async { one() }
val two = async { two() }.await()

将并行执行one()two()

所以也许这可以总结为:只有那些协程可以在等待中并行执行,到那时 known/spawned。

val one = async { one() }
val two = async { two() }
val int1 = one.await()
val int2 = two.await()

这是做什么的:

  1. 刷新任务一
  2. 重生任务二
  3. 等待任务一
  4. 等待任务二

val one = async { one() }.await()
val two = async { two() }.await()

这是做什么的:

  1. 刷新任务一
  2. 等待任务一
  3. 重生任务二
  4. 等待任务二

这里没有并发,纯粹是顺序代码。事实上,对于顺序执行,你甚至不应该使用 async。正确的成语是

val one = withContext(Dispatchers.Default) { one() }
val two = withContext(Dispatchers.Default) { two() }

在 Marko Topolnik 的回答之后,我尝试了不同的变体,我认为它是可以接受的答案。但一件有趣的事情是,如果我启动协程而不调用 await,函数开始但不会结束。下面是我的代码。

fun main(args: Array<String>) = runBlocking {

    var time = measureTimeMillis {
        val one = async { one(1) }
        val two = async { two(1) }
        val int1 = one.await()
        val int2 = two.await()
    }

    println("time: $time")


    time = measureTimeMillis {
        val one = async { one(2) }.await()
        val two = async { two(2) }.await()
    }

    println("time: $time")

    time = measureTimeMillis {
        val one = async { one(3) }
        val two = async { two(3) }
    }

    println("time: $time")



}

suspend fun one(iteration: Int): Int {
    println("func1 start, iteration $iteration")
    delay(200)
    println("func1 end, iteration $iteration")
    return 12
}

suspend fun two(iteration: Int): Int {
    println("func2 start, iteration $iteration")
    delay(400)
    println("func2 end, iteration $iteration")
    return 23
}

输出是,

func1 开始,迭代 1
func2 开始,迭代 1
func1 结束,迭代 1
func2 结束,迭代 1
时间:430
func1 开始,迭代 2
func1 结束,迭代 2
func2 开始,迭代 2
func2 结束,迭代 2
时间:607
func1 开始,迭代 3
时间:2
func2 开始,迭代 3

进程已完成,退出代码为 0

经验法则:

  • 不需要并行执行时使用withContext
  • 仅在需要并行执行时才使用asyncwithContextasync 都可用于获得启动无法获得的结果。
  • 使用 withContext 来 return 单个任务的结果。'
  • 对并行 运行 的多个任务的结果使用异步。

查看this了解更多详情