検証環境
この記事の内容は、以下の環境で検証しました。
- 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は結果をあとで渡せるのは大きいです。
図解するとこんな感じです。
公式サイトには残り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の定義も気になります。
少し確認してみます。
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を使っているんですね。
public interface Deferred<out T> : Job {
}
Jobを拡張したものだったのか。。。
launchとJobの明確な違いがわかったけど、使いみちがよくわからない。
ただ、DeferredはJobを継承しているので、Deferredのほうが良いようなきがしてきました。
まとめ
このブロックで理解できたことは以下のことだと思います。
- async関数を呼び出すとコルーチンが生成される
- Deferredには後で結果がはいる
- Deferredのawaitを呼び出すと処理を待機する