🌽
【Android】コルーチンの基本概念について
コルーチンの基本概念のCoroutineScope、Job、CoroutineContextについて調べてみました。
コルーチンについて
- 並行処理のデザインパターン
- 処理を一旦中断したあと続きから処理を再開できるので、非同期処理を簡略化できる
- コルーチンは必ずいずれかのスコープに属する
- コルーチンは新しくスコープを作成する
CoroutineScope
- 最初の土台となるスコープ
- まず
CoroutineScopeを作成して、このスコープの中で子となるコルーチンを起動する -
launchまたはasync[1]を使って作成したコルーチンを追跡・管理する -
cancel()を呼び出すことで、コルーチンをキャンセルする- キャンセルは親から子に伝播するので、
CoroutineScope内で起動したコルーチンもキャンセルされる
- キャンセルは親から子に伝播するので、
-
CoroutineScopeはCoroutineContextを持っていて、コルーチンで新しく起動したスコープは、親となるCoroutineScopeのCoroutineContextを引き継ぐ-
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
- コルーチンのライフサイクルや、作業の開始・完了の状態を管理する
- コルーチンを一意に識別する
-
launch、asyncで起動したコルーチンはJob - 新しいコルーチンは
Jobのインスタンスを取得するので、親と子のJobが同じになることはない
CoroutineContext
キーと値のペアでコルーチンに関する情報を持っている。
CoroutineContextの要素
- よく使われる要素は、
DispatcherとJob-
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) {
// 新しいコルーチン
}
オーバーライド前
オーバーライド後(※ Job はオーバーライドされない)
-
launchとasyncはCoroutineScopeの拡張関数なので、コルーチンの起動にはCoroutineScopeが必要。 ↩︎
Discussion