1. 序章

このチュートリアルでは、 CoroutineContext について学習し、CoroutineContextの重要な要素の1つとしてディスパッチャーを続行します。

2. コルーチンとは何ですか?

コルーチンは、協調マルチタスクを可能にするサブルーチンまたはプログラムです。 したがって、コルーチンを一時停止または再開したり、別のコルーチンに譲ったりすることができます。 Kotlinでは、関数の前の suspend キーワードは、呼び出し元を一時停止し、コルーチン内でのみ呼び出すことができることを意味します。

いくつかのコルーチンビルダー関数があります:launchおよびasync CoroutineScope の拡張、およびrunBlocking

3. コルーチンコンテキスト

すべてのコルーチンには、 CoroutineContext が関連付けられています。これは、Elementのインデックス付きセットです。 では、インデックス付きセットとは何ですか? これは、セットとマップの混合物です。つまり、各要素に固有のキーを持つセットです。 また、 CoroutineContext#get は、異種要素のルックアップで型安全性を提供するため、注目に値します。

public operator fun <E : Element> get(key: Key<E>): E?

すべてのコルーチンクラスはCoroutineScopeを実装し、プロパティcoroutineContextを持っています。 したがって、コルーチンブロックのcoroutineContextにアクセスできます。

runBlocking {
    Assertions.assertNotNull(coroutineContext)
}

そして、coroutineContextの要素を読み取ることができます。

runBlocking {
    Assertions.assertNotNull(coroutineContext[Job])
}

3.1. コルーチンコンテキストを操作する方法は?

CoroutineContext は不変ですが、要素を追加するか、1つを削除するか、2つの既存のコンテキストをマージすることで、新しいコンテキストを作成できます。 また、要素のないコンテキストは、EmptyCoroutineContextのインスタンスとして作成できます。

plus (+)演算子を使用して、2つのCoroutineContextをマージできます。 ここで注目すべき設計は、Elementのインスタンスがそれ自体でシングルトンのCoroutineContextであるということです。 したがって、コンテキストに要素を追加することで、新しいコンテキストを簡単に作成できます。

val context = EmptyCoroutineContext
val newContext = context + CoroutineName("baeldung")
Assertions.assertTrue(newContext != context)
Assertions.assertEquals("baeldung", newContext[CoroutineName]!!.name)

または、 CoroutineContext#minusKey を呼び出すことにより、CoroutineContextから要素を削除できます。

val context = CoroutineName("baeldung")
val newContext = context.minusKey(CoroutineName)
Assertions.assertNull(newContext[CoroutineName])

3.2. コルーチンコンテキスト要素

Kotlinには、コルーチンのさまざまな側面を永続化および管理するためのCoroutineContext.Elementの実装が多数あります。

  • デバッグ: CoroutineName CoroutineId
  • ライフサイクル管理: Job 。タスク階層を格納し、ライフサイクルの管理に使用できます。
  • 例外処理: CoroutineExceptionHandler は、例外を伝播しないlaunchなどのコルーチンビルダー内で発生した例外を処理します
  • スレッド管理: ContinuousInterceptor 。コルーチン内の継続をリッスンし、その再開をインターセプトします。 CoroutineDispatcher の実装は、このカテゴリで最もよく使用されるタイプです。 さらに、ContinuousInterceptorのデフォルト要素はDispatchers.Defaultです。

4. コーディネーター

CoroutineDispatcher は、コンテキストのContinuousInterceptor要素のサブタイプです。 したがって、コルーチンの実行スレッドを決定する責任があります。

Kotlinがコルーチンを実行するとき、最初に CoroutineDispatcher#isDispatchNeededtrueを返すかどうかをチェックします。 はいの場合、 CoroutineDispatcher#dispatchが実行スレッドを割り当てます。 それ以外の場合、Kotlinはコルーチンを制限なしで実行します。

KotlinにはCoroutineDispatcherの実装がいくつかあり、内部シングルトンインスタンスがいくつかあります: DefaultScheduler CommonPool DefaultExecutor 、および Unconfined

事前定義されたスケジューラーを渡すために、kotlinx.coroutines.Dispatchers値を使用できます。

  • Dispatchers.Default :システムプロパティ kotlinx.coroutines.scheduler を設定しないか有効にしない場合、DefaultSchedulerシングルトンを指します。 それ以外の場合は、CommonPoolシングルトンを指します。
  • Dispatchers.Main :メインディスパッチャーをロードし、必要な依存関係がクラスパスに存在する場合にのみ使用できます。
  • Dispatchers.Unconfined Unconfinedシングルトンへのポインター
  • Dispatchers.IO DefaultScheduler.IOへのポインター

ディスパッチャーをコルーチンビルダー関数に渡してみましょう。

launch(Dispatchers.Default) {
    Assertions.assertTrue(
      coroutineContext[ContinuationInterceptor]!!
        .javaClass
        .name.contains("DefaultScheduler")
    )
}

さらに、 ThreadPoolDispatcher.kt には、2つの廃止されたパブリック関数があります。シングルスレッド実行用の newSingleThreadContext と、スレッドプールを割り当てるためのnewFixedThreadPoolContextです。 代わりに、 ExecutorService のインスタンスを作成し、それをCoroutineDispatcherとして渡すことができます。

launch(Executors.newSingleThreadExecutor().asCoroutineDispatcher()) {
    Assertions.assertTrue(
      coroutineContext[ContinuationInterceptor]!!
        .javaClass
        .name.contains("ExecutorCoroutineDispatcher")
    )
}

4.1. 閉じ込められた対。 制限のないディスパッチャ

デフォルトでは、ディスパッチャーをビルダー関数に明示的に渡さない限り、ディスパッチャーは外部のCoroutineScopeから継承されます。

runBlocking(Executors.newSingleThreadExecutor().asCoroutineDispatcher()) {
    launch {
        Assertions.assertTrue(
          coroutineContext[ContinuationInterceptor]!!
            .javaClass
            .name.contains("ExecutorCoroutineDispatcher")
        )
        Assertions.assertTrue(Thread.currentThread().name.startsWith("pool"))
    }
}

一方、 Dispatchers.Unconfined は、内部オブジェクト Unconfined を参照します。これは、 CoroutineDispatcher#isDispatchNeededfalseでオーバーライドします。

override fun isDispatchNeeded(context: CoroutineContext): Boolean = false

これにより、コルーチンは、コルーチンがサスペンドブロックを呼び出すまで呼び出し元スレッドで開始し、その後、サスペンド関数のスレッドを再開します。

runBlocking {
    launch(Dispatchers.Unconfined) {
        Assertions.assertTrue(Thread.currentThread().name.startsWith("main"))
        delay(10)
        Assertions.assertTrue(!Thread.currentThread().name.startsWith("main"))
    }
}

Dispatchers#Unconfined には、CPUを集中的に使用せず、共有データを更新しないコルーチンが適しています。

5. コルーチンスコープ

以前の説明から理解できるように、 CoroutineScope は、coroutineContextという1つのプロパティのみを持つインターフェイスです。 さらに、コルーチンビルダー関数( asyncおよびlaunchと呼ばれるCoroutineScopeの拡張)を使用してコルーチンを構築できます。 両方のビルダー関数は、次の3つのパラメーターを要求します。

  • context (オプション):何も渡されなかった場合、デフォルトはEmptyCoroutineContextです。
  • coroutineStart (オプション):何も渡されなかった場合、 CoroutineStart#DEFAULTと見なされます。 その他の利用可能なオプションは、 LAZY ATOMIC 、およびUNDISPATCHEDです。
  • suspend ブロック:コルーチン内の実行可能コードブロック

私たちの関心はcontext引数にあります。 新しいコルーチンのコンテキストを作成するために、ビルダー関数は context引数を現在のCoroutineScope#coroutineContext に追加してから、いくつかの構成要素を追加します。

次に、ビルダーはAbstractCoroutineの実装の1つからコルーチンインスタンスを作成します。

  • 起動の場合: StandaloneCoroutine 、または LazyStandaloneCoroutine
  • async の場合: DeferredCoroutine 、または LazyDeferredCoroutine

次に、ビルダーはコンストラクターで新しいコンテキストを渡します。

AbstractCoroutine のコンテキストは、 parentContext (前のステップのコンテキスト)とコルーチン自体です。 AbstractCoroutineCoroutineScopeJobの両方であるため、コルーチンコンテキストにはJob要素が含まれます。

public final override val context: CoroutineContext = parentContext + this

GlobalScopeはシングルトンCoroutineScopeですが、制限のあるジョブがなく、EmptyCoroutineContextがあります。 コルーチンビルダーでの使用は避ける必要がありますが、トップレベルのコルーチンまたは制限のないコルーチンで使用できます。

6. 結論

この記事では、CoroutineContextディスパッチャーについて学習しました。

いつものように、記事のサンプルの完全なコードは、GitHubから入手できます。