LoginSignup
4
1

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

Concurrent using async

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

今回のタイトルはわかりやすいです。
asyncってやつを使うと並列処理ができるってイメージでしょうか。

内容を読み進めます。

What if there are no dependencies between invocation of doSomethingUsefulOne and doSomethingUsefulTwo and we want to get the answer faster, by doing both concurrently? This is where async comes to help.

Conceptually, async is just like launch. It starts a separate coroutine which is a light-weight thread that works concurrently with all the other coroutines. The difference is that launch returns a Job and does not carry any resulting value, while async returns a Deferred – a light-weight non-blocking future that represents a promise to provide a result later. You can use .await() on a deferred value to get its eventual result, but Deferred is also a Job, so you can cancel it if needed.

訳すと

doSomethingUsefulOne関数とdoSomethingUsefulTwo関数の処理がお互い依存していない場合、並列処理したほうが早いのではないだろうか。そんな時に便利なのが、 async 関数です。

async関数とlanch関数は似通っています。async関数は別コルーチンを起動します。async関数で起動するコルーチンも他のコルーチンと同様に軽量のスレッドです。
違いとしては、lauch関数はJobを返しますが、async関数はDeferredを返します。Deferredはasync関数内の処理が完了するとDeferredに結果が格納されます。async関数にとってDeferredは完了後に結果を格納する 約束 です。Deferredのawaitを呼び出すと結果がでるまで待機します。
Jobと同様にDeferredも処理のキャンセルができる

ここまで読むと対してlaunchとの違いが無い気がします。サンプルコードを書いたほうが早い気がします。

早速コードをみてみます。

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

fun main() = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async { doSomethingUsefulOne() }
        val two = async { doSomethingUsefulTwo() }
        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
}

最初の説明にあったとおり、launchがasyncになっていて、joinがawaitになっています。
実行結果も見てみましょう。

The answer is 42
Completed in 1047 ms

各関数で1秒ずつ待っているけど、トータルの時間は1秒ちょっとですね。並列に動いている事が確認できます。Jobとの違いとして、Deferredは結果をあとで渡せるのは大きいです。

図解するとこんな感じです。

asyncとawait.png

公式サイトには残り1行分あります。最後の文も読んでみます。

This is twice as fast, because we have concurrent execution of two coroutines. Note, that concurrency with coroutines is always explicit.

意訳込みですが、(略)

2つのコルーチンで実行されるため、明示的に並行処理されているのがわかります。速度も2倍です。

私の考察と同じだったのですこし安心しました。

asyncとDefferedの定義も気になります。
少し確認してみます。

async関数の定義
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

やはり、CoroutineContextを使っているんですね。

Defferedの定義
public interface Deferred<out T> : Job {
}

Jobを拡張したものだったのか。。。

launchとJobの明確な違いがわかったけど、使いみちがよくわからない。
ただ、DeferredはJobを継承しているので、Deferredのほうが良いようなきがしてきました。

まとめ

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

  • async関数を呼び出すとコルーチンが生成される
  • Deferredには後で結果がはいる
  • Deferredのawaitを呼び出すと処理を待機する
4
1
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
4
1