Kotlin 1.3: コルーチンの監督(スーパバイザ)


Kotlin 1.3 から使えるようになる Coroutine のドキュメントを読み、 まとめました。

キャンセルはコルーチンの階層全体において双方向の伝播関係にあります。 しかし、方向の指定されていないキャンセルが必要になった場合はどうしましょうか。

その良い例が 定義されたジョブを持つ UI コンポーネント です。 もし UI の子タスクが失敗した場合、 UI全体において常にキャンセル(効果的な停止)が必要なわけではありません。 UIコンポーネントが削除された場合、そしてそのジョブがキャンセルされている場合、全ての子のジョブは結果が必要なくなったため、失敗する必要があります。

他の例では、いくつかの子のジョブを生成し、その実行を監督し、失敗を追跡し、失敗した子のジョブを再起動するサーバープロセスです。

監督ジョブ

これらの目的のためには SupervisionJob が使えます。 これは例外が下方にのみ伝播される通常のジョブに似ています。 例を見てみます。

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

公式ドキュメントとは異なる結果となりました。 最初の子ジョブがキャンセルされており、ふたつめの子ジョブが実行されていないので、コードは意図した挙動になっています。

delay を使ってもう少しゆっくりと眺めてみます。

このようにすると、出力は公式ドキュメントと同じになりました。 ふたつめの子ジョブを実行するスキを与えてやったことになります。

監督スコープ

scoped の同時実行性

スコープ付き同時実行には coroutineScope の代わりに supervisorScope が利用可能です。 supervisorScope は一方向のみにキャンセルを伝播し、自身が失敗した場合にのみすべての子を取り消します。 これは、 coroutineScope と同様に、 完了前にすべての子の完了を待ちます。

この公式ドキュメントのコードを実行すると次のようになりました。

公式ドキュメントとは結果が異なりました。 上と同じように、 yield の前後に delay(500L) を挿入すると、 公式ドキュメントに書かれている結果と同じになりました。

監督コルーチンでの例外

他に、一般のジョブと監督ジョブ(スーパバイザジョブ)との大きな違いとして例外処理があります。 スーパバイザジョブを使う場合、 子の例外が親に伝播されないため、 全ての子は例外を子自身が例外処理のメカニズムを使って処理する必要があります。

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

もちろん、子のジョブから例外ハンドラを取り去ると、エラーがそのまま出力されます。

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

supervisorScope は、 引数を1つしか取らないので、 例外ハンドラなどは渡せないようになっています。