🌽
【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
。
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) {
// 新しいコルーチン
}
オーバーライド前
オーバーライド後(※
Job
はオーバーライドされない)
-
launch
とasync
はCoroutineScope
の拡張関数なので、コルーチンの起動にはCoroutineScope
が必要。 ↩︎
Discussion