この記事を書く背景
coroutineとはなんぞやとか、書き方とかなんとなく勉強をしていたのですが、仕事でとうとうRxやめてcoroutineを使うことになったので色々参考にしつつ入門します。
coroutineとは
引用させてもらいます。
coroutineとは何かを一言で表現すると、「中断可能な計算インスタンス」になります。ここでいう計算インスタンスとは、 Thread クラスのインスタンスのように特定のコードブロックが紐付いたインスタンスのことです。coroutineもインスタンスを作成し、紐付いたコードブロックを実行するという点では Thread と同じです。しかし、一度 start したらそのまま最後までコードブロックが実行される Thread とは異なり、coroutineは実行の途中で処理を中断することができ、またその中断したところからそのままの状態で実行を再開することができます。
なんとなく理解できたが、聞きなれない単語が出てきた。
そもそも中断可能とは?
↓この辺りがjavaの動きから、coroutineの仕組みを学習できました m(_ _)m
Rxとの比較
coroutineはRxJavaの代替ではありません。coroutineは前述のように「中断可能な計算インスタンス」ですが、RxJavaは「データフローを宣言的に書くリアクティブプログラミング用フレームワーク」です。こうして並べてみると比べるものではないことは一目瞭然ですが、それでは何故比較されているかといえば、RxJavaはPromiseパターンのスーパーセットでもあり、coroutineはPromiseパターンを同期的に書く手法(async/awaitパターン)を提供しているからです。特にAndroid開発においては標準の非同期処理の手法が使いづらかったため、RxJavaはリアクティブプログラミングのためにではなく、単なるPromiseとして活用されているケースが多くありました。そのようなケースにおいては確かにasync/awaitは代替となりうることから、本来同じ抽象レイヤーにいないこの2者が比較されることになったのです。最近は有識者による啓蒙活動によりこの誤解は解けつつありますが、きちんと理解しておきたいポイントです。
確かに使い所は似ているけれど、概念的な部分では別のレイヤーにある気がします。
サンプルアプリの作成
実際にサンプルコードを作って動かしていきたいと思います。
環境
runBlocking
code
現在のスレッドをブロックするビルダーです。任意の型を返します。
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
Log.d(TAG,"1")
runBlocking {
Log.d(TAG,"2")
}
Log.d(TAG,"3")
D/CoroutineSampleActivity: 1
D/CoroutineSampleActivity: 2
D/CoroutineSampleActivity: 3
launch
code
現在のスレッドをブロックしないため、現在のスレッドの処理が先に行われ、launch内の処理が後で実行されプログラムが終了します。
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
Log.d(TAG,"4")
GlobalScope.launch {
Log.d(TAG,"5")
}
Log.d(TAG,"6")
D/CoroutineSampleActivity: 4
D/CoroutineSampleActivity: 6
D/CoroutineSampleActivity: 5
join
コルーチン内で他のコルーチンの終了を待機するために Job#join が利用できます。
code
runBlocking {
Log.d(TAG,"7")
GlobalScope.launch {
Log.d(TAG, "8")
}.join()
Log.d(TAG,"9")
}
D/CoroutineSampleActivity: 7
D/CoroutineSampleActivity: 8
D/CoroutineSampleActivity: 9
async & await
async の返す Deferred インタフェースには await メソッドが定義されています。
await メソッドは、 async が起動したコルーチンの終了まで現在のコルーチンを中断し、終了したコルーチンの戻り値を取得します。
code
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
runBlocking {
Log.d(TAG,"10")
async {
Log.d(TAG, "11")
}.await()
Log.d(TAG,"12")
}
D/CoroutineSampleActivity: 10
D/CoroutineSampleActivity: 11
D/CoroutineSampleActivity: 12
suspend
suspend modifier を付けて関数を宣言することで、その関数が Coroutine を中断可能であることを示すことができます。
suspend と宣言された関数は Coroutine 内または他の Suspending Function 内からしか呼び出すことができません。
また Coroutine を開始するには最低 1 つの Suspending Function がなければなりません。
suspend fun getProfile(){
delay(1000)
Log.d(TAG,"getProfile")
}
runBlocking {
async {
getProfile()
}.await()
}
//1秒後に出力される
D/CoroutineSampleActivity: getProfile