Edited at

Futureにハマる

More than 3 years have passed since last update.

以下の何の変哲もないコード。

メインスレッドは早々に終了しないようにAwaitでFutureの終了を待っている。1s後に「The result is 4」と表示される。

うん、期待通り。めでたしめでたし。

import scala.concurrent._

import ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.util.{ Success, Failure }

object Main extends App {
val n = 2

val f: Future[Int] = Future {
Thread.sleep(1000)
n * 2
}
f.onComplete {
case Success(r) => println("The result is " + r)
case Failure(t) => println(t.getMessage)
}

Await.ready(f, Duration.Inf)
}

ではこれだとどうか?

onComplete部分での表示に溜めを作ってみる。重い処理と読み替えてもいい。

  f.onComplete {

case Success(r) =>
println("The result is ...")
Thread.sleep(1000)
println(r)
case Failure(t) => println(t.getMessage)
}

1s後に「The result is ...」が表示されて、更に1s待って「4」と表示され...ない。「The result is ...」は出るけど。

というのが今回のハマりどころ。

ドキュメントをよく読んでみるとonCompleteはimplicitパラメータとしてExecutionContextを必要とするのでした。

つまりメインとは別スレッドとして実行される。

確認のためスレッドidを出力するようにしてみる。

object Main extends App {

println("main: " + Thread.currentThread.getId) // ここ

val n = 2

val f: Future[Int] = Future {
println("future: " + Thread.currentThread.getId) // ここ
Thread.sleep(1000)
n * 2
}
f.onComplete {
case Success(r) =>
println("onSuccess: " + Thread.currentThread.getId) // ここ
println("The result is ...")
Thread.sleep(1000)
println(r)
case Failure(t) => println(t.getMessage)
}

Await.ready(f, Duration.Inf)
}

結果はこう。

main: 28

future: 29
onSuccess: 29

やはり。

つまり、AwaitでFutureの終了自体は待つけども、success時の(failure時の)処理は別スレッドで処理されるので結局非同期に動き処理途中でもメインスレッドは終了してしまう。

冒頭のコードはロスタイムっぽい時間でたまたま動いていたということか。

というわけで、プログラムを変更。

object Main extends App {

val n = 2

val f: Future[Int] = Future {
Thread.sleep(1000)
n * 2
}

Await.ready(f, Duration.Inf)

println("The result is ...")
Thread.sleep(1000)
f.value.get match {
case Success(r) => println(r)
case Failure(t) => println(t.getMessage)
}
}

これでFutureの終わりをAwaitで待ってから、その後の処理をメインスレッドで実行することになるので重い処理か軽い処理かとか気にすることなく期待通りの結果が得られるようになった。