🌽

【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

Job

コルーチンのライフサイクルを制御する

CoroutineDispatcher

動作させるスレッドを指定する

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

ログインするとコメントできます