こんにちは、kiyokawaです。
10月30日にKotlin1.3がリリースされ、ついにコルーチンが正式採用となりました。
コルーチンを使うことで非同期処理を簡単に分かりやすく記述できます。
とりあえず触ってみました。
導入方法
コルーチンはライブラリとして用意されているので、まずはapp配下のgradleに以下を追加します。
1 2 3 4 |
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0' } |
使い方
まずは、一番簡単なコルーチンの起動方法です。
1 2 3 |
GlobalScope.launch { // 処理 } |
これだけで、コルーチンを起動することが出来ます。
実行するスレッドはlaunchの引数にDispatcherを渡すことで指定できます。
1 2 3 4 5 6 7 |
// メインスレッドで実行されます。 Dispatchers.Main // 計算処理向けの環境で実行されます。 // 何も指定せずに起動した場合はこのDispatcherが使われます。 Dispatchers.Default // IO向けの環境で実行されます。 Dispatchers.IO |
早速launchでコルーチンを起動し使ってみます。
1 2 3 4 5 6 7 |
fun sample() { println("start") GlobalScope.launch { println("hello, coroutines!") } println("end") } |
実行結果はこうなります。
1 2 |
start end |
何故なら、コルーチンは処理をブロックしないため、コルーチン内の処理が走る前に呼び出し側が終了してしまったからです。
以下のように、runBlockingを使うことで処理の中にsuspend関数として用意されているdelay関数を使い処理を待つことで順番に出力されます。
1 2 3 4 5 6 7 8 9 |
fun sample() = runBlocking { println("start") GlobalScope.launch { delay(1000L) println("hello, coroutines!") } delay(2000L) println("end") } |
ただ、このrunBlockingは現在のスレッドをブロックします。
またこちらはmain関数やテストへの橋渡しとして用意されているようなので、普段使いすることは避けたほうがよさそうです。
コールバックを置き換える
コルーチンはlaunchの他にasyncというものも用意されています。
違いとしては、launchは戻り値を持たず、asyncは戻り値を持っているということです。
使い方としては以下のような感じになるかと思います。
1 2 3 4 5 |
fun setLocalUser() = GlobalScope.launch(Dispatchers.Main) { // await()でgetUser()の実行完了を待ち戻り値を取得する val user = async(Dispatchers.IO) { getLocalUser() }.await() textUserName.text = user.name } |
asyncを使い重い処理を非同期で行いawaitで取得することもできますが、既存ソースはだいたい非同期処理の結果はコールバックで受け取っていると思います。
非同期処理も、拡張関数としてsuspend関数(中断可能な関数)を用意してあげれば関数の利用側ではコールバックを使わずに書くことが出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// コールバック形式 fun getUser(callback: (User) -> Unit) { // 実際にはDBから取得など重い処理 val user = User("kiyokawa", 19) callback(user) } // suspend関数化 suspend fun UserDao.getUser(): User = suspendCoroutine { continuation -> getUser { user -> // 呼び出しもとの処理を再開 continuation.resume(user) } } |
上のように定義することで利用側は以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 |
// コールバック形式 fun setLocalUser() { dao.getUser { user -> textUserName.text = user.name } } // suspend関数化 fun setLocalUser() = GlobalScope.launch(Dispatchers.Main) { val user = dao.getUser() textUserName.text = user.name } |
コールバック分のネストが減って分かりやすくなりました。
今はコールバックが一つなので威力が伝わりにくいかも知れませんが、コールバックのネストがあるところなどでは一気にスッキリします。
一つ注意点があり、suspend関数はsuspend関数内もしくはコルーチン内でしか呼ぶことが出来ません。
触ってみて
コルーチンを実際に触れてみて、理解が足りない部分もまだまだありますがコールバック地獄を解消していくのは気持ちよく、コードの見通しも良くなりました。
コルーチンを使えばAndroid開発で面倒なパーミッションリクエストなども同期的に書けるようになります。
最近は非同期処理はほとんどRxJava2を使って書いていますが、少しずつコルーチンも取り込んでみようかなと思いました。