web-dev-qa-db-ja.com

コトリンコルーチン非同期待機シーケンス

これら2つのコードブロックの違いを教えてください。初回は421を印刷しますが、2回目は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
}
5
toffor
val one = async { one() }
val two = async { two() }
val int1 = one.await()
val int2 = two.await()

これが何をするか:

  1. タスク1を生成する
  2. タスク2を生成する
  3. タスク1を待つ
  4. タスク2を待つ

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

これが何をするか:

  1. タスク1を生成する
  2. タスク1を待つ
  3. タスク2を生成する
  4. タスク2を待つ

ここには並行性はありません。純粋にシーケンシャルなコードです。実際、シーケンシャル実行の場合、asyncを使用するべきではありません。適切なイディオムは

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

最初のバリアントでは、両方の非同期呼び出しに対してDeferred<Int>を取得します。 Deferredのドキュメント が示すように、遅延オブジェクトが存在する可能性のある状態がいくつかあります。外部からは、その状態はnewまたはactiveのいずれかですが、まだ完了していません。ただし、2番目のバリアントでは、最初のasync-awaitには既にcompleted状態が必要です。そうでない場合は、そこに値を設定できません。ただし、async{one()}.await()では、2番目のasyncはまだ不明です。また、await()の戻り値はIntではなくDeferredになっているため、それまでにコルーチンが実行されている必要があります。 await()のドキュメント も確認してください。

言い換えると:

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

onetwoの両方がDeferred<Int>になりました。まだ呼び出されていません(またはまだ呼び出されている可能性があります)。 one.await()を呼び出すと、すぐにonetwoの両方が開始される場合があります(コード内でtwo.await()を使用していなくても)。

ただし、2番目のバリアントでは:

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

async {one()}のコルーチンを作成しても、await()を呼び出しているため、すぐにoneに値を設定する必要があります。 onetwoのタイプは両方ともIntです。したがって、これらの行の最初の行がヒットするとすぐに、非同期コードをすぐに実行する必要があります。それまでに、最初の値を待つ間に別の非同期呼び出しを実行する必要があることを誰も知りません。最初のものがawaitを持たない場合、コルーチンは再び並列に実行されます、例えば:

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

one()two()を並行して実行します。

したがって、これを要約すると、それらのコルーチンのみが待機中に並行して実行でき、それまでに既知/生成されます。

1
Roland

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で終了しました

0
toffor