LoginSignup
12
15

More than 5 years have passed since last update.

Kotlin の Coroutines を5分で理解する

Last updated at Posted at 2018-12-29

Kotlin の Coroutines を理解するのに何も難しいことはありません。そう、JavaScript の async/await がわかればね。

まず、 1 から 10 までの値を合算する処理があったとします。

fun main(args: Array<String>) {
    val numList = (1..10)
    val sum = numList.reduce { acc, v -> acc + v }
    println(sum) // 55
}

JS でいう Promise は Coroutines の世界では Deferred という名称です。これを生成する関数を用意します。

import kotlinx.coroutines.*

fun createDeferred(value: Int): Deferred<Int> {
    return GlobalScope.async {
        delay(1000)
        value    
    }
}

上記は JS でいうと下記のようなコードに相当します。

function createPromise(value) {
  return new Promise(resolve => setTimeout(() => resolve(value), 1000));
}

GlobalScope.async で非同期処理を行う Deferred を生成できる」と理解すれば一旦は OK です。

Deferred を捌く main() は下記のように変わります。

suspend fun main(args: Array<String>) { // *1
    val deferredList = (1..10).map { v -> createDeferred(v) } // *2
    val numList = deferredList.awaitAll() // *3
    val sum = numList.reduce { acc, v -> acc + v }
    println(sum) // 55 (約1秒遅れ)
}
  • *1 ... suspend funasync function 相当だという理解で OK
  • *2 ... 変数 deferredList の型は List<Deferred<Int>>
  • *3 ... await Promise.all() みたいなことをやっているという理解で OK

上記のコードは途中で await をしているので、関数宣言に suspend を付けないとエラーになります。

ここまでで正常系は理解できました。
ではエラーハンドリングはどうしたらいいでしょうか。

まず createDeferred() を下記のように変更します。

import kotlin.random.Random

fun createDeferred(value: Int): Deferred<Int> {
    return GlobalScope.async {
        delay(1000)
        when (Random.nextFloat() < 0.9) { // *1, *2
            true -> value
            else -> throw Exception("Error for $value")
        }
    }
}
  • *1 ... Random.nextFloat()0 以上 1 未満の数値を返す
  • *2 ... when でパターンマッチングを表現できる

これにより createDeferred() で返される Deferred は10%の確率でエラーになります。

となると main() ではエラーハンドリングをしないといけません。

suspend fun main(args: Array<String>) {
    val deferredList = (1..10).map { v -> createDeferred(v) }
    try {
        val numList = deferredList.awaitAll()
        val sum = numList.reduce { acc, v -> acc + v }
        println(sum) // 55
    } catch (e: Exception) {
        println(e) // "Error for n"
    }
}

何も難しいことはなく、await するところを try catch で囲むだけです。
このコードでは10%の確率でエラーになるものを10個束ねているので、ほとんどのケースで catch の中を通ってエラーが出力されますが、たまに成功して 55 が出力されます。

最終的に全容はこのようになりました。

import kotlinx.coroutines.*
import kotlin.random.Random

suspend fun main(args: Array<String>) {
    val deferredList = (1..10).map { v -> createDeferred(v) }
    try {
        val numList = deferredList.awaitAll()
        val sum = numList.reduce { acc, v -> acc + v }
        println(sum)    
    } catch (e: Exception) {
        println(e)
    }
}

fun createDeferred(value: Int): Deferred<Int> {
    return GlobalScope.async {
        delay(1000)
        when (Random.nextFloat() < 0.9) {
            true -> value
            else -> throw Exception("Error for $value")
        }
    }
}

( https://pl.kotl.in/H1LuMeSWV ← 上記コードを実際に動かせる Kotlin Playground )

ちなみに Deferred は下記のようにして個別に await することもできます。

suspend fun main(args: Array<String>) {
    val deferredList = (1..10).map { v -> createDeferred(v) }
    val numList = deferredList.map { d -> 
        try {
            d.await()
        } catch (e: Exception) {
            println(e)
            0
        }
    }
    val sum = numList.reduce { acc, v -> acc + v }
    println(sum)
}

以上です。

とても簡単に Coroutines を理解することができました。

特に TypeScript に慣れていれば型の書き方はほとんど同じだし、非同期処理に関しては用語が違うくらいなのでとっつきやすいと思いました。

12
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
15