🌽
【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