GlobalScope vs CoroutineScope vs lifecycleScope

Nov 25 2020

私はAsyncTaskその単純さのためにそれを扱い、かなりよく理解することに慣れています。しかしCoroutines、私には混乱しています。次のそれぞれの違いと目的を簡単に説明していただけますか?

  1. GlobalScope.launch(Dispatchers.IO) {}
  2. GlobalScope.launch{}
  3. CoroutineScope(Dispatchers.IO).launch{}
  4. lifecycleScope.launch(Dispatchers.IO){}
  5. lifecycleScope.launch{}

回答

6 Thracian Nov 28 2020 at 07:40

まず、明確にするために定義から始めましょう。コルーチンとコルーチンフローのチュートリアルまたは遊び場が必要な場合は、私が作成したこのチュートリアル/遊び場を確認できます。

Scope コルーチンを起動するために使用するオブジェクトであり、 CoroutineContext

public interface CoroutineScope {
    /**
     * The context of this scope.
     * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
     * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
     *
     * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
     */
    public val coroutineContext: CoroutineContext
}

コルーチンコンテキストは、コルーチンの実行方法を定義する一連のルールと構成です。内部的には、これは一種のマップであり、可能なキーと値のセットが含まれています。

コルーチンコンテキストは不変ですが、セットに要素を追加するのと同じように、plus演算子を使用してコンテキストに要素を追加し、新しいコンテキストインスタンスを生成できます。

コルーチンの動作を定義する要素のセットは次のとおりです。

  • CoroutineDispatcher —適切なスレッドに作業をディスパッチします。
  • ジョブ—コルーチンのライフサイクルを制御します。
  • CoroutineName —デバッグに役立つコルーチンの名前。
  • CoroutineExceptionHandler —キャッチされなかった例外を処理します

ディスパッチャディスパッチャは、使用するスレッドプールを決定します。ディスパッチャクラスもCoroutineContextであり、CoroutineContextに追加できます。

  • Dispatchers.Default:大きなリストの並べ替え、複雑な計算の実行など、CPUを集中的に使用する作業。JVM上のスレッドの共有プールがそれをサポートします。

  • Dispatchers.IO:ネットワークまたはファイルからの読み取りと書き込み。要するに–名前が示すように、任意の入力と出力

  • Dispatchers.Main:AndroidのメインスレッドまたはUIスレッドでUI関連のイベントを実行するための必須のディスパッチャー。

たとえば、RecyclerViewでのリストの表示、ビューの更新などです。

あなたは、チェックアウトすることができますAndroidの公式文書をディスパッチャ詳細は。

公式文書には次のように記載されていますが、編集してください

Dispatchers.IO-このディスパッチャは、メインスレッドの外部でディスクまたはネットワークI / Oを実行するように最適化されています。例としては、Roomコンポーネントの使用、ファイルの読み取りまたは書き込み、ネットワーク操作の実行などがあります。

マルコトポルニックからの回答

IOは、特別で柔軟なスレッドプールでコルーチンを実行します。これは、呼び出し元のスレッドをブロックするレガシーのブロックIOAPIを使用せざるを得ない場合の回避策としてのみ存在します。

どちらかが正しいかもしれません。

ジョブコルーチン自体はジョブによって表されます。ジョブはコルーチンへのハンドルです。(起動または非同期によって)作成するすべてのコルーチンについて、コルーチンを一意に識別し、そのライフサイクルを管理するJobインスタンスを返します。ジョブをCoroutineScopeに渡して、そのライフサイクルを管理することもできます。

コルーチンのライフサイクル、キャンセル、および親子関係を担当します。現在のジョブは、現在のコルーチンのコンテキストから取得できます。ジョブは、新規、アクティブ、完了、完了、キャンセル、キャンセルの一連の状態を通過できます。状態自体にアクセスすることはできませんが、ジョブのプロパティ(isActive、isCancelled、およびisCompleted)にアクセスできます。

CoroutineScopeこれは、CoroutineContextsを引数として取り、結合されたCoroutineContextのラッパーを作成する単純なファクトリ関数として定義されています。

public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

internal class ContextScope(context: CoroutineContext) : CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    // CoroutineScope is used intentionally for user-friendly representation
    override fun toString(): String = "CoroutineScope(coroutineContext=$coroutineContext)"
}

Job提供コンテキストに要素がまだない場合は、要素を作成します。

GlobalScopeのソースコードを見てみましょう

/**
 * A global [CoroutineScope] not bound to any job.
 *
 * Global scope is used to launch top-level coroutines which are operating on the whole application lifetime
 * and are not cancelled prematurely.
 * Another use of the global scope is operators running in [Dispatchers.Unconfined], which don't have any job associated with them.
 *
 * Application code usually should use an application-defined [CoroutineScope]. Using
 * [async][CoroutineScope.async] or [launch][CoroutineScope.launch]
 * on the instance of [GlobalScope] is highly discouraged.
 *
 * Usage of this interface may look like this:
 *
 * ```
 * fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) {
 *     for (number in this) {
 *         send(Math.sqrt(number))
 *     }
 * }
 * ```
 */
public object GlobalScope : CoroutineScope {
    /**
     * Returns [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

あなたが見ることができるようにそれは伸びます CoroutineScope

1- GlobalScopeは、アプリが存続している限り存続します。たとえば、このスコープでカウントを実行し、デバイスを回転させると、タスク/プロセスが続行されます。

GlobalScope.launch(Dispatchers.IO) {} 

アプリが動作している限り実行されますが、使用しているためIOスレッドで実行されます Dispatchers.IO

2-最初のものと同じですが、デフォルトでは、コンテキストがない場合、起動はDispatchers.Defaultを使用するEmptyCoroutineContextを使用するため、違いは最初のものとのスレッドのみです。

3-これは最初のものと同じですが、構文が異なります。

4-lifecycleScopeは、LifeCycleOwnerActivityまたはFragmentのlifCycleの拡張であり、そのアクティビティまたはフラグメントが破棄されるとスコープがキャンセルされます。

/**
 * [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
 *
 * This scope will be cancelled when the [Lifecycle] is destroyed.
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
 */
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

これを次のように使用することもできます

class Activity3CoroutineLifecycle : AppCompatActivity(), CoroutineScope {

    private lateinit var job: Job

    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main + CoroutineName("🙄 Activity Scope") + CoroutineExceptionHandler { coroutineContext, throwable ->
            println("🤬 Exception $throwable in context:$coroutineContext") } private val dataBinding by lazy { Activity3CoroutineLifecycleBinding.inflate(layoutInflater) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(dataBinding.root) job = Job() dataBinding. button.setOnClickListener { // This scope lives as long as Application is alive GlobalScope.launch { for (i in 0..300) { println("🤪 Global Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")
                    delay(300)
                }
            }

            // This scope is canceled whenever this Activity's onDestroy method is called
            launch {
                for (i in 0..300) {
                    println("😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this") withContext(Dispatchers.Main) { dataBinding.tvResult.text = "😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this"
                    }
                    delay(300)
                }
            }
        }

    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }

}
3 MarkoTopolnik Nov 29 2020 at 14:41

私はあなたのリストを3つの軸に沿って整理します:

  1. GlobalScopeCoroutineScope()対。lifecycleScope
  2. Dispatchers.IO vs.継承された(暗黙の)ディスパッチャ
  3. スコープ内のディスパッチャとの引数として指定します。 launch

1.スコープの選択

Kotlinのコルーチンに対する考え方の大部分は、構造化された同時実行性です。つまり、すべてのコルーチンは、依存関係に従う階層に編成されています。バックグラウンド作業を開始する場合、現在の「作業単位」がまだアクティブである間、つまり、ユーザーがそこから移動しておらず、もう気にしない間に、ある時点で結果が表示されると想定します。その結果。

Androidでは、lifecycleScopeUIアクティビティ間でユーザーのナビゲーションを自動的に追跡することができるため、結果がユーザーに表示されるバックグラウンド作業の親として使用する必要があります。

また、ファイアアンドフォーゲットの作業があり、最終的には終了する必要がありますが、ユーザーはその結果を待ちません。このためにWorkManagerは、ユーザーが別のアプリケーションに切り替えた場合でも安全に実行できるAndroidまたは同様の機能を使用する必要があります。これらは通常、ローカル状態をサーバー側に保持されている状態と同期させるタスクです。

この図でGlobalScopeは、基本的に構造化された同時実行からの脱出ハッチです。それはあなたがスコープを供給するという形を満たすことを可能にしますが、それが実装することになっているすべてのメカニズムを打ち負かします。GlobalScopeキャンセルすることはできず、親もいません。

CoroutineScope(...).launchすぐに忘れてしまう親なしでスコープオブジェクトを作成し、それをキャンセルする方法がないため、書き込みは間違っています。使用するのと似てGlobalScopeいますが、さらにハッキーです。

2.ディスパッチャの選択

コルーチンディスパッチャは、コルーチンを実行できるスレッドを決定します。Androidには、注意が必要な3つのディスパッチャーがあります。

  1. Mainすべてを単一のGUIスレッドで実行します。それはあなたの主な選択でなければなりません。
  2. IO特別で柔軟なスレッドプールでコルーチンを実行します。これは、呼び出し元のスレッドをブロックするレガシーのブロックIOAPIを使用せざるを得ない場合の回避策としてのみ存在します。
  3. Defaultスレッドプールも使用しますが、サイズは固定されており、CPUコアの数と同じです。GUIでグリッチを引き起こすのに十分な時間がかかる計算集約型の作業(たとえば、画像の圧縮/解凍)に使用します。

3.ディスパッチャを指定する場所

まず、使用しているコルーチンスコープで指定されているディスパッチャーに注意する必要があります。GlobalScopeは何も指定しないため、一般的なデフォルトであるDefaultディスパッチャが有効になります。ディスパッチャをlifecycleScope指定しますMain

CoroutineScopeコンストラクターを使用してアドホックスコープを作成するべきではないことはすでに説明したので、明示的なディスパッチャーを指定する適切な場所は、のパラメーターとしてlaunchです。

技術的な詳細では、を書くsomeScope.launch(someDispatcher)とき、someDispatcher引数は実際には本格的なコルーチンコンテキストオブジェクトであり、たまたま単一の要素であるディスパッチャがあります。起動するコルーチンは、コルーチンスコープ内のコルーチンとパラメーターとして指定したコルーチンを組み合わせることにより、それ自体の新しいコンテキストを作成します。その上、それはJobそれ自体のために新鮮なものを作成し、それをコンテキストに追加します。ジョブは、コンテキストで継承されたジョブの子です。

2 SiddharthKamaria Nov 28 2020 at 07:44

TL; DR

  1. GlobalScope.launch(Dispatchers.IO):でトップレベルのコルーチンを起動しDispatchers.IOます。コルーチンはバインドされておらず、終了またはキャンセルされるまで実行を続けます。プログラマーはjoin()またはへの参照を維持する必要があるため、しばしば推奨されませんcancel()

  2. GlobalScope.launch:上記と同じですが、指定されていない場合はGlobalScope使用しDispatchers.Defaultます。しばしば落胆します。

  3. CoroutineScope(Dispatchers.IO).launchDispatchers.IOディスパッチャーがコルーチンビルダーで指定されていない限り、を使用するコルーチンスコープを作成します。launch

  4. CoroutineScope(Dispatchers.IO).launch(Dispatchers.Main):ボーナス1。上記と同じコルーチンスコープを使用しますが(スコープインスタンスが同じ場合)、このコルーチンをオーバーライドDispatcher.IOしますDispatchers.Main

  5. lifecycleScope.launch(Dispatchers.IO):AndroidXが提供するlifecycleScope内でコルーチンを起動します。コルーチンは、ライフサイクルが無効になるとすぐにキャンセルされます(つまり、ユーザーがフラグメントから移動します)。Dispatchers.IOスレッドプールとして使用します。

  6. lifecycleScope.launch:上記と同じDispatchers.Mainですが、指定されていない場合は使用します。

外植

コルーチンスコープは構造化された同時実行を促進します。これにより、同じスコープで複数のコルーチンを起動し、必要に応じてスコープをキャンセルできます(これにより、そのスコープ内のすべてのコルーチンがキャンセルされます)。逆に、A GlobalScopeコルーチンは、あなたが次への参照を保持する必要があるスレッドに似ているjoin()cancel()、それ。これは、RomanElizarovによるMediumに関する優れた記事です。

CoroutineDispatcherは、launch {}どのスレッドプールを使用するかについてコルーチンビルダー(この場合)に指示します。利用可能な事前定義されたディスパッチャがいくつかあります。

  • Dispatchers.Default-CPUコアの数に相当するスレッドプールを使用します。CPUバウンドワークロードに使用する必要があります。
  • Dispatchers.IO-64スレッドのプールを使用します。スレッドが通常待機しているIOバウンドワークロードに最適です。おそらくネットワーク要求またはディスクの読み取り/書き込み用です。
  • Dispatchers.Main(Androidのみ):メインスレッドを使用してコルーチンを実行します。UI要素の更新に最適です。

上記の6つのシナリオに対応する6つの関数を含む小さなデモフラグメントを作成しました。以下のフラグメントをAndroidデバイスで実行する場合。フラグメントを開き、フラグメントを残します。GlobalScopeコルーチンのみがまだ生きていることに気付くでしょう。ライフサイクルが無効な場合、ライフサイクルコルーチンはlifecycleScopeによってキャンセルされます。一方、CoroutineScopeのものはonPause()、私たちが明示的に行った呼び出しでキャンセルされます。

class DemoFragment : Fragment() {

    private val coroutineScope = CoroutineScope(Dispatchers.IO)

    init {
        printGlobalScopeWithIO()
        printGlobalScope()
        printCoroutineScope()
        printCoroutineScopeWithMain()
        printLifecycleScope()
        printLifecycleScopeWithIO()
    }

    override fun onPause() {
        super.onPause()
        coroutineScope.cancel()
    }

    private fun printGlobalScopeWithIO() = GlobalScope.launch(Dispatchers.IO) {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[GlobalScope-IO] I'm alive on thread ${Thread.currentThread().name}!") } } private fun printGlobalScope() = GlobalScope.launch { while (isActive) { delay(1000) Log.d("CoroutineDemo", "[GlobalScope] I'm alive on ${Thread.currentThread().name}!")
        }
    }
    
    private fun printCoroutineScope() = coroutineScope.launch {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[CoroutineScope] I'm alive on ${Thread.currentThread().name}!") } Log.d("CoroutineDemo", "[CoroutineScope] I'm exiting!") } private fun printCoroutineScopeWithMain() = coroutineScope.launch(Dispatchers.Main) { while (isActive) { delay(1000) Log.d("CoroutineDemo", "[CoroutineScope-Main] I'm alive on ${Thread.currentThread().name}!")
        }
        Log.d("CoroutineDemo", "[CoroutineScope-Main] I'm exiting!")
    }

    private fun printLifecycleScopeWithIO() = lifecycleScope.launch(Dispatchers.IO) {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[LifecycleScope-IO] I'm alive on ${Thread.currentThread().name}!") } Log.d("CoroutineDemo", "[LifecycleScope-IO] I'm exiting!") } private fun printLifecycleScope() = lifecycleScope.launch { while (isActive) { delay(1000) Log.d("CoroutineDemo", "[LifecycleScope] I'm alive on ${Thread.currentThread().name}!")
        }
        Log.d("CoroutineDemo", "[LifecycleScope] I'm exiting!")
    }

}
i30mb1 Nov 28 2020 at 08:36

suspend関数を起動する場合は、で実行する必要があることを知っておく必要がありますCoroutineScope。すべてCoroutineScopeが持っていCoroutineContextます。(適切なスレッドへのディスパッチ作業)、(コルーチンのライフサイクルの制御)、(キャッチされなかった例外の処理)、(デバッグに役立つコルーチンの名前)CoroutineContextを含むことができるマップはどこにありますか。DispatcherJobCoroutineExceptionHandlerCoroutineName

  1. GlobalScope.launch(Dispatchers.IO) {}-GlobalScope.launchグローバルコルーチンを作成し、キャンセルしてはならない操作に使用しますが、より良い代替方法は、Applicationクラスにカスタムスコープを作成し、それを必要とするクラスに注入することです。これには、テスト用にを使用CoroutineExceptionHandlerまたは交換できるという利点がありますCoroutineDispatcher
  2. GlobalScope.launch{}-と同じですがGlobalScope.launch(Dispatchers.IO) {}、で実行さcoroutinesDispatchers.Defaultます。コンテキストでディスパッチャが指定されていない場合に使用されるDispatchers.DefaultデフォルトですDispatcher
  3. CoroutineScope(Dispatchers.IO).launch{}-1つのパラメーターでスコープを作成coroutineし、IOスレッドで新しく起動します。発射されたオブジェクトで破壊されます。しかし、あなたは手動で呼び出す必要があります.cancel()のためにCoroutineScopeあなたが適切にあなたの仕事を終了させたい場合。
  4. lifecycleScope.launch(Dispatchers.IO){}-LifecycleまたはLifecycleOwnerActivityまたはFragment)から利用できる既存のスコープであり、依存関係を使用してプロジェクトに組み込まれますandroidx.lifecycle:lifecycle-runtime-ktx:*。これを使用すると、手動で作成する必要がなくなりますCoroutineScope。それはあなたの仕事Dispatchers.IOをブロックすることなく実行しますMainThread、そしてあなたlifecycleが破壊されたときにあなたの仕事がキャンセルされることを確認してください。
  5. lifecycleScope.launch{}-デフォルトのパラメータlifecycleScope.launch(Dispatchers.IO){}を使用CoroutinesScopeして作成し、で実行するのと同じです。つまりDispatchers.MaincoroutinesDispatcher.Main作業できますUI