Scala

Futureをマスターする -コンパニオンオブジェクト操作-

More than 3 years have passed since last update.

コンパニオンオブジェクトFutureの全メソッド網羅いきます。


シリーズ目次

はじめに

コンパニオンオブジェクト操作

インスタンス操作

おまけ

Futureにハマる


Futureの単独生成&並列実行


apply (※要ExecutionContext、別スレッドでの実行)

通常はapplyを明示的に呼ぶことはないでしょう。以下のように使用。

val f: Future[String] = Future {

"hello"
}

なお、ネットでよく見るサンプルも公式ドキュメントもfuture{...}で生成しているものばかりなのですが、Scala2.11からscala.concurrentのfutureメソッドはdeprecated扱いになっており、代わりにFutureを使うよう記述されています。

よって本記事ではfutre{ ... }は使用せずFuture{ ... }で統一しています。


Futureの単独生成(実行済みFuture)

実行済みFutureを作成するため並列実行は行われない。よってAwaitによる待ちは不要。

当然、valueで呼び出してもNoneではなく全てSomeである。


successful

scala.util.Successなvalueを生成。

val f: Future[Int] = Future.successful(100)


failed

scala.util.Failureなvalueを生成。

val f: Future[String] = Future.failed{ new Exception("error in future") }


fromTry

TryからFutureを作成する。

val f: Future[String] = Future.fromTry(Try{ 

if (...) "hello" else throw new Exception("error in f")
})


Futureの複数生成&複数の並列実行


traverse (※要ExecutionContext、別スレッドでの実行)

TraversableOnceなものからFutureを複数生成&実行。

5だけ意図的に遅らせているが、結果は終わった順ではなく、traverseで処理した順に返ってくる。

よってこの結果は1,2,3,4,5,6,7,8,9,10,11,12となる。

  val f: Future[List[Int]] = Future.traverse((1 to 12).toList) { i =>

Future {
println(s"start: $i")
if (i == 5) Thread.sleep(5000) else Thread.sleep(1000)
println(s"end: $i")
i
}
}
f.onSuccess{ case result: List[Int] => println(result.mkString(",")) }

Await.ready(f, Duration.Inf)

4コアCPUのMacBookの環境で実行すると、4つずつ同時に処理されることが確認できる。


sequence (※要ExecutionContext、別スレッドでの実行)

traverseの簡易版で、引数にFutureのTraversableOnceを取る。

結果については上記と同様。

  val futures: List[Future[Int]] = (1 to 12).toList.map { i =>

Future {
println(i)
Thread.sleep(1000)
i
}
}

val f: Future[List[Int]] = Future.sequence(futures)
f.onSuccess{ case result => println(result.mkString) }

Await.ready(f, Duration.Inf)


Futureの複数生成&複数並列実行&特定の条件を満たしたFutureをひとつ返す


firstCompletedOf (※要ExecutionContext、別スレッドでの実行)

並列実行するFutureの中で最初に終わったFutureをひとつ返す。

3で例外が出るが、4の方が先に終わるのでonSuccessコールバックのほうが呼ばれる。なお、3と4の処理を入れ替えれば、onFailureコールバックのほうが呼ばれる。

  val futures: Seq[Future[Int]] = Seq(

Future{ Thread.sleep(3000); 1 },
Future{ Thread.sleep(2000); 2 },
Future{ Thread.sleep(1000); throw new Exception("error in 3") },
Future{ Thread.sleep(500); 4 })

val f = Future.firstCompletedOf(futures)
f.onSuccess{ case result: Int => println(result) }
f.onFailure{ case t => println(t.getMessage()) }

Await.ready(f, Duration.Inf)


find (※要ExecutionContext、別スレッドでの実行)

第一引数にFutureのTraversableOnce、第二引数にBooleanを返す関数を指定し、それを満たす最初のFutureが返る。

以下の例の場合は最初に処理が終わるのは4だが、条件は奇数のものなのでこの場合は3が返る。

なお、例外が起きるFutureがある場合の振る舞いはfirstCompletedOfと同じ。

  val futures: Seq[Future[Int]] = Seq(

Future{ Thread.sleep(3000); 1 },
Future{ Thread.sleep(2000); 2 },
Future{ Thread.sleep(1000); 3 },
Future{ Thread.sleep(500); 4 })

val f = Future.find(futures){ _ % 2 == 1 }
f.onSuccess{ case result: Option[Int] => println(result.get) }

Await.ready(f, Duration.Inf)


Futureの複数生成&複数並列実行&全てのFutureの結果のまとめあげ


fold (※要ExecutionContext、別スレッドでの実行)

各Futureの結果を畳み込み演算する。

以下は初期値10から初めて、畳み込んだ結果、20が返ってくる。

なお、ひとつでも例外が起きるFutureがいるとonFailureコールバックが呼ばれる。

  val futures: Seq[Future[Int]] = Seq(

Future{ Thread.sleep(3000); 1 },
Future{ Thread.sleep(2000); 2 },
Future{ Thread.sleep(1000); 3 },
Future{ Thread.sleep(500); 4 })

val f = Future.fold(futures)(10){ (total, v) => total + v }
f.onSuccess{ case result: Int => println(result) }

Await.ready(f, Duration.Inf)


reduce (※要ExecutionContext、別スレッドでの実行)

初期値なしのfold。0から始まるので結果は10となる。

なお、ひとつでも例外が起きるFutureがいるとonFailureコールバックが呼ばれる。

  val futures: Seq[Future[Int]] = Seq(

Future{ Thread.sleep(3000); 1 },
Future{ Thread.sleep(2000); 2 },
Future{ Thread.sleep(1000); 3 },
Future{ Thread.sleep(500); 4 })

val f = Future.reduce(futures){ (total, v) => total + v }
f.onSuccess{ case result: Int => println(result) }

Await.ready(f, Duration.Inf)


応用:組み合わせ

10個のFutureを実行するf1とf2を作成し、それらの結果をfoldで足し合わせるfを定義。

全てが終わったら数字を足し合わせて結果を出力する。結果は220。

  val f1: Future[List[Int]] = Future.traverse((1 to 10).toList) { i =>

Future { Thread.sleep(i * 100); i }
}
val f2: Future[List[Int]] = Future.traverse((11 to 20).toList) { i =>
Future { Thread.sleep(i * 100); i }
}

val f: Future[List[Int]] = Future.fold(List(f1, f2))(List(10)){ (total, v) => v ++: total }
f.onSuccess{ case result: List[Int] => println(result.sum) }

Await.ready(f, Duration.Inf)


メソッドコンプリート状況

基本編と生成編を終えてメソッドコンプリート率は現時点では以下となりました。

trait Awaitable: 5/5

trait Future: 3/16

object Future: 10/10