Scala

Scala の Future

More than 1 year has passed since last update.


Scala の Future


前提


  • ぼくJavaもScalaも詳しくないです


参考


Futureとは


  • Scalaで非同期処理を扱うためのもの

  • 処理をブロックすることなく、結果の値を処理、合成できる

  • 非同期で行われる処理が完了すると成功または失敗が値で表現される


背景


  • Javaで提供されているマルチスレッドによる制御は共有データロックモデルで並行処理を実現する

  • ロックと共有モデルはデッドロックのリスクと戦ったりとなかなか難しいのだ


    • 頑張って実装すれば共有データとロックを隠すことはできるだろうが‥

    • 難しいというものの、java.util.concurrentを使えばスレッドを生で扱うよりは優しい



  • この難しさに対してScalaのFutureは共有データロックについてあまり考えず並行処理を実現できる


背景を踏まえた上で


  • Futureも中身的にはマルチスレッドによる非同期処理

  • Futureを利用する上でスレッドを意識することは(あんまり)ない


    • Futureとして定義した処理がどのスレッドで実行されているかを利用者が意識しなくていい




使ってみた


簡単に非同期処理されているのがわかる

import scala.concurrent.Future

import scala.concurrent.ExecutionContext.Implicits.global // おまじないです *1

Future { // Future.applyに渡したブロックが非同期で処理される
for (i <- 5 to 10) {
Thread.sleep(1)
println(s"${i}: in Future")
}
}

for (i <- 0 to 4) {
Thread.sleep(1)
println(s"${i}: in Main")
}

5: in Future

0: in Main
6: in Future
1: in Main
7: in Future
8: in Future
2: in Main
9: in Future
3: in Main
10: in Future
4: in Main


  • 出力がin Futurein Mainで入り乱れているのがわかる


完了すると成功失敗を値として表現


  • FutureのonComplateでは完了した結果を引数にしたコールバックを渡せる

  • コールバックの引数になるのはFuture[T]に対してTry[T]



    • TはFutureが成功したときの結果の型


    • Try[T]Successまたは、Failureになる。



      • SuccessのときはTの値を持ち、FailureのときはExceptionを持ち、失敗の理由がわかる





import scala.concurrent.Future

import scala.concurrent.ExecutionContext.Implicits.global // おまじないです *2

val successFuture = Future[Int] { 100 }

val failFuture = Future {
throw new RuntimeException("失敗じゃ")
}

successFuture.onComplete { // Futureの完了時にコールバックを渡せます
case Success(intValue) => println(s"future success: ${intValue}")
case Failure(_) => sys.error("ここに来ることはないだろう...")
}

failFuture.onComplete {
case Success(_) => sys.error("ここに来ることはないだろう...")
case Failure(exception) => println(s"future fail: ${exception}")
}

future success: 100

future fail: java.lang.RuntimeException: 失敗じゃ


  • onCompleteで成功失敗のケースが明確に書き分けられる!


結果をブロックすることなく処理する


  • Futureはmap,flatMap,foreach等が使えて非同期で渡って来る値を難しいことせずに扱える



val intOneFuture = Future { 1 }

def intToStringF(value: Int): Future[String] = Future { s"int: $value" }

intOneFuture.map(_ * 100).flatMap(intToStringF).foreach(println)

int: 100


まとめ


  • Futureを使うとスレッドをあんまり考えなくてもいいっぽい

  • Futureを使うと簡単に非同期処理が実現できる

  • Futureを使うと非同期処理の結果の値を簡単に処理できる


*1, *2 おまじないです


なぜ必要か


  • import scala.concurrent.ExecutionContext.Implicits.global


  • Future.applyExecutionContextとしてimplicitで必要としている


ExecutionContextとはなにか


  • Futureに渡されたコードブロックを実行する仕組みを提供するもの

  • 非同期であるというのは実装によるものでやろうと思えば同期的なFutureも作れちゃう


  • import scala.concurrent.ExecutionContext.Implicits.globalはScalaが提供しているExecutionContextの実装の一つ


    • Scala 2.12ではJavaのForkJoinPoolという実装を利用して実現しているらしい..