🌽

【Android】コルーチンの基本概念について

2021/08/10に公開

コルーチンの基本概念のCoroutineScopeJobCoroutineContextについて調べてみました。

コルーチンについて

  • 並行処理のデザインパターン
  • 処理を一旦中断したあと続きから処理を再開できるので、非同期処理を簡略化できる
  • コルーチンは必ずいずれかのスコープに属する
  • コルーチンは新しくスコープを作成する

CoroutineScope

  • 最初の土台となるスコープ
  • まずCoroutineScopeを作成して、このスコープの中で子となるコルーチンを起動する
  • launchまたはasync[1]を使って作成したコルーチンを追跡・管理する
  • cancel()を呼び出すことで、コルーチンをキャンセルする
    • キャンセルは親から子に伝播するので、CoroutineScope内で起動したコルーチンもキャンセルされる
  • CoroutineScopeCoroutineContextを持っていて、コルーチンで新しく起動したスコープは、親となるCoroutineScopeCoroutineContextを引き継ぐ
    • Jobは引き継がれない
  • CoroutineScopeを作成するときに、コンストラクタにCoroutineContextを与える

各レイヤでのCoroutineScope

viewModelScope

  • ViewModel で使う
  • ViewModel が消去されると、コルーチンが自動でキャンセルされる

lifecycleScope

  • Activity、Fragment で使う
  • onDestroy()が呼び出されると、コルーチンが自動でキャンセルされる

CoroutineScopeの作成

// CoroutineScope の作成
val scope = CoroutineScope(Job() + Dispatchers.Main)

// 子のコルーチンを起動する
val job = scope.launch {
  // 新しいスコープを作成する。このスコープは親の CoroutineContext を引き継いでいる
  val result = async {
    ...
  }.await()
}

Job

  • コルーチンのライフサイクルや、作業の開始・完了の状態を管理する
  • コルーチンを一意に識別する
  • launchasyncで起動したコルーチンはJob
  • 新しいコルーチンはJobのインスタンスを取得するので、親と子のJobが同じになることはない

CoroutineContext

キーと値のペアでコルーチンに関する情報を持っている。

CoroutineContextの要素

  • よく使われる要素は、DispatcherJob
    • CoroutineDispatcher : 動作させるスレッドを指定する
    • Job : コルーチンのライフサイクルを制御する

Dispatchers.Default

  • メインスレッドの外部で、CPU の負荷が高い処理を行うときに使う
  • 例 : リストの並び替え、JSON 解析

Dispatchers.Main

  • メインの Android スレッドで実行される。
  • UI を処理するときに使う。
  • 例 : suspend関数の呼び出し、LiveDataのアップデート

Dispatchers.IO

  • メインスレッドの外部で、ネットワークのI/Oを実行する時に使う。
  • 例 : Room の使用、ファイルの読み書き、ネットワーク接続

CoroutineName

  • コルーチンの名前を設定する
  • デバッグで使用する
val scope = CoroutineScope(Job() + Dispatchers.Main + CoroutineName("sample"))

public data class CoroutineName(
    /**
     * User-defined coroutine name.
     */
    val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
    ...
}

CoroutineExceptionHandler

キャッチされない例外を処理する

CoroutineContext+演算子を使って組み合わせる

val scope = CoroutineScope(Job() + Dispatchers.Main)

同じ種類のCoroutineContextは1つしか設定出来ないので、以下のような場合は右側の要素で上書きされる。

// 右側の「Dispatchers.IO」で上書き
val scope = CoroutineScope(Dispatchers.Main + Dispatchers.IO)

子のコルーチンで親から引き継いだCoroutineContextをオーバーライドする

以下の様にすると、親のCoroutineScopeから引き継いだCoroutineDispatcherがオーバーライドされる。

val scope = CoroutineScope(Job() + Dispathcers.Main + coroutineExceptionHandler)

// 親から引き継いだ「Dispatchers.Main」を「Dispatchers.IO」でオーバーライドする
val job = scope.launch(Dispatchers.IO) {
  // 新しいコルーチン
}

オーバーライド前

before_override

オーバーライド後(※ Job はオーバーライドされない)

after_override

脚注
  1. launchasyncCoroutineScopeの拡張関数なので、コルーチンの起動にはCoroutineScopeが必要。 ↩︎

Discussion