LoginSignup
3
2

More than 3 years have passed since last update.

Kotlin 1.3のCoroutineのサスペンド関数の合成③(ゆっくりとasyncを実行)

Posted at

検証環境

この記事の内容は、以下の環境で検証しました。

  • Intellij IDEA ULTIMATE 2018.2
  • Kotlin 1.3.0
  • Gradle Projectで作成
  • GradleファイルはKotlinで記述(KotlinでDSL)

準備

詳細は下記の準備を参照してください。
https://qiita.com/naoi/items/8abf2cddfc2cb3802daa

Lazily started async

前回に引き続き、公式サイトを読み解いていきます。

タイトルを見てみるとゆっくり実行って・・・。日本語にするとよくわからないですが、英名で考えると意外とすんなり理解できます。lazilyという単語からlazyで連想した場合、Kotlinだとあとで実行するとイメージするんではないでしょうか?
ということは、今回は、asyncを任意のタイミングで実行する方法と考えられます。

早速、公式サイトを読み進めてみます。

There is a laziness option to async using an optional start parameter with a value of CoroutineStart.LAZY. It starts coroutine only when its result is needed by some await or if a start function is invoked. Run the following example:

訳すと。

async関数のパラメータとして、startにCoroutineStart.LAZYを指定すると実行を遅らせることができます。
コルーチンの処理を開始するには、start関数を呼び出すか、他のコルーチンの処理結果を待つ場合のみです。
サンプルコードをみてみましょう。

ということは、即時で実行したい場合は、async関数に引数が必要だといことがわかりました。
また、start関数で処理を実行は理解できます。しかし、他のコルーチンの結果を待って処理を実行するイメージがつきません。

サンプルコードを確認したら理解できるかもしれません。
確認してみます。

import kotlinx.coroutines.*
import kotlin.system.*

fun main() = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
        val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
        // some computation
        one.start() // start the first one
        two.start() // start the second one
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")    
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}

async関数の引数にパラメータを渡して、start関数呼び出しで処理を開始しています。
実行結果も確認してみましょう。

The answer is 42
Completed in 1026 ms

前回と変化は無いみたいですが、並列で処理は行われているみたいです。
公式サイトには、続きがあるので、読み進めてみます。

So, here the two coroutines are defined but not executed as in the previous example, but the control is given to the programmer on when exactly to start the execution by calling start. We first start one, then start two, and then await for the individual coroutines to finish.

Note, that if we have called await in println and omitted start on individual coroutines, then we would have got the sequential behaviour as await starts the coroutine execution and waits for the execution to finish, which is not the intended use-case for laziness. The use-case for async(start = CoroutineStart.LAZY) is a replacement for the standard lazy function in cases when computation of the value involves suspending functions.

訳すと

サンプルコードでは、2つのコルーチンが実行されます。しかし、これまでとは違い実行するタイミングはプログラマーによります。今回はれいでは、1つ目のコルーチン実行後、2つ目のコルーチンが実行されています。

start関数を呼び出さず、await関数を呼び出すと、コルーチンを実行します。更に、await関数によって実行されたコルーチンの処理をまちます。
async(start = CoroutineStart.LAZY)を指定した場合、処理内にdelayが存在すると、通常の一時停止になります。

start関数を記述せずに、awaitを呼び出すと実行されるみたいです。
実際に試してみましょう。

fun main() = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
        val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
        // some computation


        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}

上記のように書き直してみました。
実行してみると。

The answer is 42
Completed in 2026 ms

実行はされているみたいですが、並列で処理はおこなわれていないですね。
やはり、関数を呼び出して完了してから次の関数を呼び出していることがわかります。

ちにみに、await関数を記述しないとどうなるかもきになります。
以下のように書き換えてみました。

import kotlinx.coroutines.*
import kotlin.system.*

fun main() = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
        val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
        // some computation


        println("The answer is ${one}")
    }

    println("Completed in $time ms")
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}

実行してみると。

The answer is LazyDeferredCoroutine{New}@5a8e6209
Completed in 8 ms

内容を確認すると、どうやらコルーチンの処理は実行されていないみたいです。
なるほどと言ったところです。

今回、async関数のstartに渡した CoroutineStart.LAZY ですが、4種類あるようです。
よくわからないものもありますが、確認してみます。
API表も意訳込みですが、訳してみます。

説明
DEFAULT Contextに従って実行します。
LAZY この記事で説明した通りです。
ATOMIC 原始的です。キャンセル不可能で、処理の文脈に従って実行します。DEFAULTによく似ていますが、実行前にキャンセルはできません。
UNDISPATCHED コルーチンがあたかもDispatchers.Unconfinedによって実行されたように、サスペンドするポイントまで実行され、再開するときにはCoroutineDispatcherよって再開されます。

まとめ

このブロックで理解できたことは以下のことだと思います。

  • async関数の引数にCoroutineStart.LAZYを指定すると任意のタイミングで実行できるコルーチンが生成できる
  • Deferredのstart関数を呼び出すと処理が始まる
  • start関数を呼び出さずにawaitを呼び出すとコルーチンを実行し、処理が終わるまで待つ
3
2
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
3
2