検証環境
この記事の内容は、以下の環境で検証しました。
- Intellij IDEA ULTIMATE 2018.2
- Kotlin 1.3.0
- Gradle Projectで作成
- GradleファイルはKotlinで記述(KotlinでDSL)
準備
詳細は下記の準備を参照してください。
https://qiita.com/naoi/items/8abf2cddfc2cb3802daa
Extract function refactoring
このブロックのタイトルは、リファクタリングのメソッドの抽出になっています。
タイトルからだけでは、内容が察せないので、早速読み進めます。
Let's extract the block of code inside launch { ... } into a separate function.
When you perform "Extract function" refactoring on this code you get a new function with suspend modifier.
That is your first suspending function.
Suspending functions can be used inside coroutines just like regular functions, but their additional feature is that they can, in turn, use other suspending functions, like delay in this example, to suspend execution of a coroutine.
意訳込みですが、訳すと以下のとおりです。
launch関数内の処理を別の関数にリファクタリングしてみましょう。
メソッド抽出のリファクタリングをすると、suspendのついたメソッドを記述することになります。それがはじめてのsuspend関数です。
Suspend関数は、通常の関数のように、コルーチンで実行されます。しかし、Suspend関数はコルーチンの処理を一時停止できます。
なるほど、suspend関数はコルーチンを一時停止できるみたいです。
まずは、サンプルコードと実行結果を見てみます。
サンプルコードは以下のとおりです。
import kotlinx.coroutines.*
fun main() = runBlocking {
launch { doWorld() }
println("Hello,")
}
// this is your first suspending function
suspend fun doWorld() {
delay(1000L)
println("World!")
}
実行結果は以下のとおりです。
Hello,
World!
実行結果を見る限りでは、これと言って変わったことはなさそうです。
ためにしに、suspendを消して実行すると以下のようになります。。
Suspend function 'delay' should be called only from a coroutine or another suspend function
意訳込みですが、suspend関数以外でsuspend関数を呼び出すには、その関数にsuspendを付与する必要があるとエラーが表示されます。
ちなみに、delay関数の定義は以下のようになっています。
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don't delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
delay関数そのものにもsuspendがふよされています。
ということは、suspend関数が呼び出せるのはsuspend関数だけであること がわかります。
さて、続きを読み進めます。
But what if the extracted function contains a coroutine builder which is invoked on the current scope? In this case suspend modifier on the extracted function is not enough.
Making doWorld extension method on CoroutineScope is one of the solutions, but it may not always be applicable as it does not make API clearer.
Idiomatic solution is to have either explicit CoroutineScope as a field in a class containing target function or implicit when outer class implements CoroutineScope.
As a last resort, CoroutineScope(coroutineContext) can be used, but such approach is structurally unsafe because you no longer have control on the scope this method is executed. Only private API can use this builder.
意訳込みですが、訳すと以下のようになります。
しかし、suspend関数内でコルーチンが定義されていた場合どうなるでしょうか?どの、スコープが保持しているコルーチンか不明になります。そのようなケースでは、抽出だけでは不十分です。
CoroutineScope内でdoWorldを拡張関数とするのも1つの解決方法ですが、同じアプリケーションで同じAPIの実装するには限界があります。
慣例的な解決方法としては、CoroutineScopeをクラスのフィールドとして保持したり、外部のクラスがCoroutineScopeを実装することです。
最終手段としては、CoroutineContextからCoroutineスコープを生成する方法があります。しかし、スコープの制御ができなくなります。
結果として、suspend関数内でコルーチンを生成しないほうが良さそうですね。
どうしようもない場合は、クラスのフィールドとして保持するのが良さそうです。
まとめ
このブロックで理解できたことは以下のことだと思います。
- コルーチンから呼び出せる関数はsuspendを付与した関数やメソッド
- suspendの付与した関数内でコルーチンは生成しないほうがよい。