Kotlin 1.3: コルーチンコンテキストとディスパッチャ (3/3)


Kotlin 1.3: コルーチンコンテキストとディスパッチャ (2/3) からの続きです。

検証環境

  • Kotlin 1.3.0-rc-146
  • kotlinx-coroutines-core 0.30.2

明示的なジョブを経由したキャンセル

知識をコンテキスト、子のコルーチン、ジョブに同時に適用してみましょう。 アプリケーションがあるライフサイクルをもつオブジェクト、ただしコルーチンではないもの、を有していると仮定します。 例えば Android のアプリケーションを書いていて、 いくつかのコルーチンを Android Activity のコンテキストで起動して、非同期処理としてデータの取得、更新、アニメーションなどを行う場合です。 全てのそれらのコルーチンは Activity が破棄された時には メモリリーク回避のために キャンセルされるべきです。

Activity のライフサイクルに結びつけたジョブインスタンスを作ることで コルーチンのライフサイクルを管理します。 Activity が作られるときに ジョブインスタンスが Job() ファクトリ関数 によって作成され、 Activity が削除される時にそのジョブがキャンセルされます。 例を示します。

この例では Activity に CoroutineScope インターフェース を実装しています。 CoroutineScope.coroutineContext プロパティ をオーバライドするだけで、 スコープ内で起動されるコルーチンのコンテキストを指定できます。 目的のディスパッチャ、この例では Dispatchers.Default、 とジョブを結合します。

これで、 明示的にコンテキストを記述することなく、 コルーチンを Activity の スコープ 内 で起動できます。 それぞれ異なる時間待機する 10 の コルーチン を 起動するコード例を示します。

メインファンクション内で Activity を作成し、 テストとして作った doSomething 関数 を呼び出します。 そして、 500 ms 後 に Activity を削除します。 その時起動された全てのコルーチンが取り消され、 待っていてもスクリーンに表示されないことが確認できます。

出力は次のようになります。

見ての通り、 最初の2つのコルーチンのみメッセージを表示していて、 Activity.destroy() 内 の job.cancel() によって、 他のものがキャンセルされています。

スレッドローカルデータ

時々、スレッドローカルなデータを渡す機能が便利になることがあります。 しかし、コルーチンにおいては特定のスレッドに固定されていないので、そのようなことを多くの定型文を使わずに達成することは困難です。

ThreadLocal, asContextElement 拡張関数 がそういったときのために存在します。 追加のコンテキスト要素を作り出し、 与えられた ThreadLocal の値を保持し、 コルーチンがコンテキストを変更する際に毎回復元します。

サンプルコードを示します。

この例では新しいコルーチンを Dispatchers.Default を利用してバックグラウンドスレッドプールの中で起動します。 起動したコルーチンはスレッドプールとは別のスレッドで動きます。 しかしそれでも threadLocal.asContextElement(value = "launch") で設定したスレッドローカルな値を保持しており、 それはコルーチンがどのスレッドで動いているかとは無関係です。 出力は次のようになります。

ThreadLocalはファーストクラスのサポートを持っており、 kotlinx.coroutines が提供するプリミティブと一緒に使用できます。 1つ重要な制限があり、スレッドローカルが変更された場合、新しい値はコルーチンの呼び出し元に伝播されず (コンテキスト要素はすべてのThreadLocalオブジェクトへのアクセスを追跡できないため)、 更新された値は次の一時停止時に失われます。 コルーチンのスレッドローカルの値を更新するにはwithContextを使用します。 詳しくは、asContextElement のドキュメントに記載があります。

あるいは、値を class Counter(var i:Int) のような変更可能なボックスに格納することができ、 その場合、スレッドローカルな変数に格納されます。 ただし、このケースでは、開発者がその変更可能なボックス内の変数に潜在的に同時発生する変更を同期する必要があります。

たとえば、MDC、トランザクションコンテキスト、またはデータを渡すためにスレッドローカルを内部的に使用する他のライブラリとの統合などの高度な使い方については、 実装する必要がある ThreadContextElementインターフェース のドキュメントに記載があります。