コルーチンを含めることができるsuspend関数
リスト2では、main()関数内に直接コルーチンを定義していました。これを、別関数に定義するとどうなるかを、次に見ていきましょう。
別の関数内でコルーチンを定義するcoroutineScopeメソッド
リスト2の(3)~(5)のコードを別関数であるlongProcess()に定義すると、リスト4のようになります。
suspend fun longProcess() { // (1)
printMsgWithTime("longProcess開始") // (2)
coroutineScope { // (3)
launch { // (4)
printMsgWithTime("コルーチン開始")
val startTime = LocalTime.now()
while(true) {
val now = LocalTime.now()
val duration = Duration.between(startTime, now)
val diff = duration.toMillis()
if(diff > 1000) {
break
}
}
printMsgWithTime("コルーチン終了")
}
}
printMsgWithTime("longProcess終了") // (5)
}
リスト4の(4)のlaunchブロックのコードはリスト2と全く同じです。こういったコルーチンを関数内で定義する場合は、以下の2点を行います。
関数をsuspendとする
リスト4の(1)の関数定義に付与されているsuspendが該当します。このキーワードを付与することで、この関数内の処理はコルーチン内での処理対象となります。
coroutineScopeメソッドを利用する
リスト4の(3)が該当します。表1の通り、suspend関数内でコルーチンを定義する場合は、coroutineScope関数を利用します。runBlocking同様に、coroutineScopeの引数ラムダ式内では暗黙的インスタンスとしてCoroutineScopeが存在することになります。
suspend関数の呼び出し方法
こうして定義したsuspend関数を実行する方法は、以下の2種類あります。
- suspend関数内から呼び出す。
- コルーチン内(launchブロック内)から呼び出す。
このことから、suspend関数はいくらでも入れ子にできますが、通常関数からは実行できず、最終的にどこかのコルーチン内(launchブロック内)から実行する必要があります。実際に、longProcess()関数をmain()関数内から実行する場合は、リスト5のコードとなり、runBlocking内のlaunch内で実行しているのがわかります。
fun main() {
runBlocking {
printMsgWithTime("main開始")
launch {
longProcess()
}
printMsgWithTime("main終了")
}
}
ここで、リスト5の実行結果を確認しておくと、リスト6のようになります。リスト4のコードと対応する番号をコメントとして記述しています。この結果を見ると、main()関数の処理が先に終了し、非同期処理となっているのがわかります。
main開始: 03:34:30.586356; 3 main終了: 03:34:30.591964; 3 longProcess開始: 03:34:30.593925; 3 // (2) コルーチン開始: 03:34:30.597843; 3 コルーチン終了: 03:34:31.598914; 3 longProcess終了: 03:34:31.599153; 3 // (5)
指定時間実行を遅らせるdelay()
ここまで、リスト2もリスト4も、1秒間の処理を行うために無限ループを利用してきました。実は、こういった指定時間処理を遅らせる関数が、Kotlinには標準で用意されています。
それが、delay()です。引数として、遅らせる時間をミリ秒で指定します。これを利用すると、longProcess()関数は、リスト7のようになります。
suspend fun longProcess() {
printMsgWithTime("longProcess開始")
delay(1000)
printMsgWithTime("longProcess終了")
}
ここでの注意点は、delay()はsuspend関数である、ということです。そのため、先述のように、内部でdelay()を利用する関数は、必ずsuspend関数とする必要があります。リスト7のlongProcess()関数シグネチャからsuspendを取り除くと、コンパイルエラーとなるので注意してください。
launchの戻り値はJob
実は、launch()メソッドには戻り値があり、Jobオブジェクトとなっています。このJobオブジェクトのcancel()メソッドを実行することで、コルーチンを途中でキャンセルできます。例えば、リスト8のコードです。
val job = launch {
longProcess()
}
:
job.cancel()
[NOTE]もうひとつのコルーチン定義であるasync
launch()の戻り値がJobということは、コルーチンから何かの実行結果として具体的な値をリターンできないことを意味します。この問題を避けるために、実は、コルーチンを定義するメソッドとして、もうひとつasyncがあります。誌面の都合上、詳細は公式ドキュメントなどの別媒体に譲りますが、このメソッドを利用することで、コルーチン内の結果を取得し利用できるようになります。
