サマリ
コンテキスト
コンテキストはスレッドのこと
withContext(Dispatchers.IO)
Dispatchers.Main
Dispatchers.IO
Dispatchers.Default
スコープ
スコープとはcoroutineが制御される範囲(追跡可能な範囲)
viewModelScope.launch
await:値を返す
※suspend 関数は、他の suspend 関数から、または、launch などのコルーチン ビルダーを使って新しいコルーチンを開始することによってのみ呼び出せる。
コンテキスト
メインセーフ
suspend 関数は、他の suspend 関数から、または、launch などのコルーチン ビルダーを使って新しいコルーチンを開始することによってのみ呼び出せる。
suspend fun fetchDocs() { // Dispatchers.Main
val result = get("developer.android.com") // Dispatchers.Main
show(result) // Dispatchers.Main
}
suspend fun get(url: String) = // Dispatchers.Main
withContext(Dispatchers.IO) { // Dispatchers.IO (main-safety block)
/* perform network IO here */ // Dispatchers.IO (main-safety block)
} // Dispatchers.Main
}
get() は引き続きメインスレッドで実行。ネットワーク リクエストを開始する前にコルーチンを停止。
ネットワーク リクエストが完了したら、get は、コールバックを使用してメインスレッドに通知するのではなく、停止したコルーチンを再開。
コルーチンを実行すべき場所
Dispatchers.Main
Dispatchers.IO
Dispatchers.Default
すべての関数をメインセーフにして、関数をメインスレッドから呼び出せるようにすることをおすすめします。そうすることで、呼び出し側において、関数を実行するためにどのスレッドを使用すべきか検討する必要がなくなります。
suspend の使用は、Kotlin に関数をバックグラウンド スレッドで実行するようにという指示ではありません。suspend 関数がメインスレッド上で動作するのは通常のことです。また、メインスレッドでコルーチンを起動することも一般的です。
実行方法
コルーチンを開始
launch は、新規コルーチンを開始し、呼び出し元に結果を返しません。
async は、新規コルーチンを開始し、await と呼ばれる中断関数で結果を返せるようにします
標準の関数は await を呼び出せないので、標準の関数から新規コルーチンを launch する必要があります。async は、別のコルーチン内部にいる場合、あるいは中断関数内部で並列分解を行う場合のみ、使用します
suspend fun fetchTwoDocs() =
coroutineScope {
val deferredOne = async { fetchDoc(1) }
val deferredTwo = async { fetchDoc(2) }
deferredOne.await()
deferredTwo.await()
}
2 つのドキュメントを非同期的にフェッチする coroutineScope だが、それぞれの遅延参照で await() を呼び出すことにより、値が返される前に両方の async オペレーションが終了することが保証されます。
CoroutineScope
CoroutineScope は、launch または async を使用して作成したコルーチンをすべて追跡します
実行中の作業(実行中のコルーチン)は、いつでも scope.cancel() を呼び出してキャンセルできます。
ディスパッチャとは異なり、CoroutineScope ではコルーチンは実行されません。
viewModelScope を使用すると、ViewModel クラスは ViewModel の onCleared() メソッドで自動的にスコープをキャンセルします。
class ExampleClass {
// Job and Dispatcher are combined into a CoroutineContext which
// will be discussed shortly
val scope = CoroutineScope(Job() + Dispatchers.Main)
fun exampleMethod() {
// Starts a new coroutine within the scope
scope.launch {
// New coroutine that can call suspend functions
fetchDocs()
}
}
fun cleanUp() {
// Cancel the scope to cancel ongoing coroutines work
scope.cancel()
}
}
Job
launch または async で作成した各コルーチンは、コルーチンを一意に識別し、そのライフサイクルを管理する Job インスタンスを返します。
class ExampleClass {
...
fun exampleMethod() {
// Handle to the coroutine, you can control its lifecycle
val job = scope.launch {
// New coroutine
}
if (...) {
// Cancel the coroutine started above, this doesn't affect the scope
// this coroutine was launched in
job.cancel()
}
}
}
CoroutineContext
CoroutineContext は、次の要素のセットを使用してコルーチンの動作を定義します。
・Job: コルーチンのライフサイクルを制御します。
・CoroutineDispatcher: 適切なスレッドに処理を送信します。
・CoroutineName: コルーチンの名前。デバッグに役立ちます。
・CoroutineExceptionHandler: キャッチされない例外を処理します。
参考
Kotlin コルーチンでアプリのパフォーマンスを改善する
https://developer.android.com/kotlin/coroutines/coroutines-adv?hl=ja
スコープ
ViewModelScope
ViewModel が消去されると自動的にキャンセル
viewModelScope.launch {
}
LifecycleScope
Lifecycle が破棄されたときにキャンセル
viewLifecycleOwner.lifecycleScope.launch {
}
LifecycleScopeは長期間になるので、特定の状態のときにコードブロックの実行の開始・終了できる
viewLifecycleOwner.lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Trigger the flow and start listening for values.
// This happens when lifecycle is STARTED and stops
// collecting when the lifecycle is STOPPED
viewModel.someDataFlow.collect {
// Process item
}
}
}
・・・
viewLifecycleOwner.lifecycleScope.launch {
exampleProvider.exampleFlow()
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect {
// Process the value.
}
}
・・・
lifecycleScope.launch {
whenStarted {
}
}
・・・
lifecycleScope.launchWhenStarted {
}
LiveDataを使用した非同期的な処理
LiveData が有効になると開始し、LiveData が無効になったときに設定可能なタイムアウトが経過すると自動的にキャンセル
完了前に自動的にキャンセルされた場合は、LiveData が再びアクティブになると再開
LiveData 値が設定されるまで、emit() 呼び出しによりブロックの実行が停止
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
・・・
private val userId: LiveData<String> = MutableLiveData()
val user = userId.switchMap { id ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
emit(database.loadUserById(id))
}
}
参考
ライフサイクル対応コンポーネントで Kotlin コルーチンを使用する
https://developer.android.com/topic/libraries/architecture/coroutines?hl=ja