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