LoginSignup
77
68

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-06-30

コンパニオンオブジェクト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

77
68
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
77
68