Kotlinコルーチンの起動/参加と非同期/待機の違いは何ですか


回答:


232
  • launchコルーチンを起動して忘れるのに使用されます。新しいスレッドを開始するようなものです。内のコードlaunchが例外で終了する場合、それはスレッドでキャッチされない例外のように扱われます-通常、バックエンドJVMアプリケーションのstderrに出力され、Androidアプリケーションをクラッシュさせます。join起動されたコルーチンの完了を待機するために使用され、その例外を伝播しません。ただし、クラッシュしたコルーチンは、対応する例外を付けて親をキャンセルします。

  • async結果を計算するコルーチン開始するために使用されます。結果はのインスタンスで表され、その上で使用Deferredする必要がありますawaitasyncコード内のキャッチされなかった例外は結果の内部に格納され、Deferred他の場所には配信されません。処理されない限り、黙ってドロップされます。あなたはasyncで始めたコルーチンを忘れてはいけません


1
AsyncはAndroidのネットワーク呼び出しに適したコルーチンビルダーですか?
Faraaz 2017年

適切なコルーチンビルダーは、達成しようとしていることによって異なります
Roman Elizarov

9
「非同期で始めたコルーチンを忘れてはならない」について詳しく説明できますか?たとえば、予期しない落とし穴はありますか?
Luis

2
「非同期コード内のキャッチされない例外は、結果のDeferred内に格納され、他のどこにも配信されません。処理されない限り、通知なしでドロップされます。」
ローマンエリザロフ2017

9
非同期の結果を忘れると、終了してガベージコレクションされます。ただし、コードのバグが原因でクラッシュした場合は、そのことを知ることはできません。それが理由です。
ローマエリザロフ2018年

77

このガイドhttps://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.mdは役に立ちます。重要な部分を引用します

🦄 コルーチン

基本的に、コルーチンは軽量スレッドです。

したがって、コルーチンは、非常に効率的な方法でスレッドを管理するものと考えることができます。

🐤 発売

fun main(args: Array<String>) {
    launch { // launch new coroutine in background and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello,") // main thread continues while coroutine is delayed
    Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}

したがってlaunch、バックグラウンドスレッドを開始し、何かを実行して、トークンをとしてすぐに返しますJobjoinこれJobを呼び出して、このlaunchスレッドが完了するまでブロックすることができます

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = launch { // launch new coroutine and keep a reference to its Job
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    job.join() // wait until child coroutine completes
}

🦆 非同期

概念的には、非同期は起動と同じです。他のすべてのコルーチンと同時に動作する軽量スレッドである別のコルーチンを開始します。違いは、launchはJobを返し、結果の値を運びませんが、asyncはDeferred(後で結果を提供するという約束を表す軽量のノンブロッキングフューチャー)を返すことです。

したがってasync、バックグラウンドスレッドを開始し、何かを実行して、トークンをとしてすぐに返しますDeferred

fun main(args: Array<String>) = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async { doSomethingUsefulOne() }
        val two = async { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")
}

遅延値で.await()を使用して最終的な結果を取得できますが、遅延もジョブなので、必要に応じてキャンセルできます。

ですからDeferred実際にJobです。https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.htmlを参照してください

interface Deferred<out T> : Job (source)

🦋 非同期はデフォルトで熱心です

CoroutineStart.LAZYの値を持つオプションの開始パラメーターを使用して非同期にする遅延オプションがあります。コルーチンを開始するのは、その結果が何らかの待機によって必要になった場合、または開始関数が呼び出された場合のみです。


12

launchそしてasync新しいコルーチンを開始するために使用されています。しかし、彼らは異なる方法でそれらを実行します。

違いを非常に簡単に理解するのに役立つ非常に基本的な例を示したいと思います

  1. 打ち上げ
    class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btnCount.setOnClickListener {
            pgBar.visibility = View.VISIBLE
            CoroutineScope(Dispatchers.Main).launch {
                val currentMillis = System.currentTimeMillis()
                val retVal1 = downloadTask1()
                val retVal2 = downloadTask2()
                val retVal3 = downloadTask3()
                Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1}, ${retVal2}, ${retVal3} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
                pgBar.visibility = View.GONE
            }
        }

    // Task 1 will take 5 seconds to complete download
    private suspend fun downloadTask1() : String {
        kotlinx.coroutines.delay(5000);
        return "Complete";
    }

    // Task 1 will take 8 seconds to complete download    
    private suspend fun downloadTask2() : Int {
        kotlinx.coroutines.delay(8000);
        return 100;
    }

    // Task 1 will take 5 seconds to complete download
    private suspend fun downloadTask3() : Float {
        kotlinx.coroutines.delay(5000);
        return 4.0f;
    }
}

この例では、私のコードはbtnCountボタンをクリックして3つのデータをpgBarダウンロードし、すべてのダウンロードが完了するまで進行状況バーを表示しています。3つのがありsuspend機能downloadTask1()downloadTask2()およびdownloadTask3()ダウンロードデータは。それをシミュレートするために、delay()これらの関数で使用しました。これらの関数は5 seconds8 secondsとを5 secondsそれぞれ待機します。

launchこれらのサスペンド機能を開始するために使用したlaunchように、これらは順次(1つずつ)実行されます。これは、が完了したdownloadTask2()downloadTask1()に開始し、が完了したdownloadTask3()downloadTask2()にのみ開始することを意味します。

出力スクリーンショットのようにToast、3つのダウンロードすべてを完了するための合計実行時間は、5秒+ 8秒+ 5秒= 18秒になります。launch

起動例

  1. 非同期

見てきたように、3つのタスクすべてlaunchが実行さsequentiallyれます。すべてのタスクを完了する時間はでした18 seconds

これらのタスクが独立していて、他のタスクの計算結果を必要としない場合は、実行することができますconcurrently。それらは同時に開始し、バックグラウンドで同時に実行されます。これはで行うことができますasync

asyncDeffered<T>タイプのインスタンスをT返します。ここで、中断関数が返すデータのタイプです。例えば、

  • downloadTask1()返すDeferred<String>文字列は、関数の戻り値の型であるとして、
  • downloadTask2()戻ってくるDeferred<Int>のInt関数の戻り値の型であるとして、
  • downloadTask3()戻ってくるDeferred<Float>フロートは、関数の戻り値の型であるとして、

asynctypeのreturnオブジェクトを使用して、type Deferred<T>の戻り値を取得できTます。それはawait()通話で行うことができます。例として以下のコードを確認してください

        btnCount.setOnClickListener {
        pgBar.visibility = View.VISIBLE

        CoroutineScope(Dispatchers.Main).launch {
            val currentMillis = System.currentTimeMillis()
            val retVal1 = async(Dispatchers.IO) { downloadTask1() }
            val retVal2 = async(Dispatchers.IO) { downloadTask2() }
            val retVal3 = async(Dispatchers.IO) { downloadTask3() }

            Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1.await()}, ${retVal2.await()}, ${retVal3.await()} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
            pgBar.visibility = View.GONE
        }

このようにして、3つのタスクすべてを同時に起動しました。したがって、完了するまでの合計実行時間は、3つのタスクすべての中で最大8 secondsとなる時間downloadTask2()です。次のスクリーンショットでこれを見ることができますToast message

例を待つ


1
これlaunch逐次的なファンのためのものでありasync同時実行の
Akbolat SSS

すべてのタスクで1回の起動を使用し、各タスクで非同期を使用しました。それぞれが別のコルーチンで起動され、誰かを待たないので、多分それはより速いですか?これは誤った比較です。通常、パフォーマンスは同じです。主な違いの1つは、起動では、所有者を分割する非同期ではなく、常に新しいコルーチンが開始されることです。もう1つの要因は、何らかの理由で非同期タスクの1つが失敗すると、親コルーチンも失敗することです。そのため、非同期は発売ほど人気が​​ありません。
p2lem8dev

1
この答えは正しくありません。非同期を起動の代わりにサスペンド機能と直接比較します。例でサスペンド関数を直接呼び出す代わりに、launch(Dispatchers.IO){downloadTask1()}を呼び出すと、両方が順次ではなく同時に実行されることがわかります。出力を取得することはできませんが、順次ではありません。また、deferred.await()を連結せず、deferred.await()を個別に呼び出さない場合は、非同期がシーケンシャルであることがわかります。
トラキア

2
-1これは明らかに間違っています。両方ともlaunchasync新しいコルーチンを開始します。子供のいない単一のコルーチンと3人の子供を持つ単一のコルーチンを比較しています。async呼び出しのそれぞれを置き換えることができlaunch、同時実行性に関しては何も変更されません。
モイラ

この答えの外来ノイズは、通常のトピックの範囲外である複雑さを追加しています。
TruthAdjuster

6
  1. 両方のコルーチンビルダー、つまりlaunchとasyncは基本的に、タイプがCoroutineScopeのレシーバーを持つラムダです。つまり、それらの内部ブロックは中断関数としてコンパイルされます。したがって、どちらも非同期モードで実行され、どちらもブロックを順次実行します。

  2. 起動と非同期の違いは、2つの異なる可能性を可能にすることです。Launch Builderはジョブを返しますが、非同期関数はDeferredオブジェクトを返します。launchを使用して、ブロックからの戻り値を期待しないブロックを実行できます。つまり、データベースへの書き込み、ファイルの保存、または基本的にその副作用のために呼び出されたものの処理です。一方、前に述べたようにDeferredを返すasyncは、データをラップするオブジェクトであるブロックの実行から有用な値を返すため、主に結果に使用できますが、副作用にも使用できます。注:関数awaitを使用して遅延オブジェクトを取り除き、その値を取得できます。これにより、値が返されるか、例外がスローされるまでステートメントの実行がブロックされます。

  3. コルーチンビルダー(起動と非同期)はどちらもキャンセルできます。

  4. 何か他にありますか:はい、起動時にブロック内で例外がスローされた場合、コルーチンは自動的にキャンセルされ、例外が配信されます。一方、それが非同期で発生した場合、例外はそれ以上伝播されず、返されたDeferredオブジェクト内でキャッチ/処理する必要があります。

  5. コルーチンの詳細 https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1


1
このコメントをありがとう。スレッドのすべてのポイントを集めました。すべてのローンチがキャンセルされるわけではないことを付け加えます。たとえば、Atomicはキャンセルできません。
p2lem8dev

4

打ち上げは仕事を返します

asyncは結果を返します(据え置きジョブ)

ジョイン付きの起動は、ジョブが完了するまで待機するために使用されます。それは、join()を呼び出すコルーチンを一時停止し、その間、現在のスレッドが他の作業(別のコルーチンの実行など)を行えるようにします。

asyncは、いくつかの結果を計算するために使用されます。コルーチンを作成し、Deferredの実装として将来の結果を返します。結果の遅延オブジェクトがキャンセルされると、実行中のコルーチンがキャンセルされます。

文字列値を返す非同期メソッドを考えます。asyncメソッドがawaitなしで使用された場合、Deferred文字列を返しますが、awaitが使用された場合、結果として文字列を取得します

非同期と起動の主な違い。Deferredは、コルーチンの実行が終了した後にT型の特定の値を返しますが、Jobは返しません。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.