Scala の Future
前提
- ぼくJavaもScalaも詳しくないです
参考
- 本家 https://docs.scala-lang.org/ja/overviews/core/futures.html
- コップ本 https://www.amazon.co.jp/Scala%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%A9%E3%83%96%E3%83%AB%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E7%AC%AC3%E7%89%88-Martin-Odersky/dp/4844381490/ref=sr_1_sc_1?ie=UTF8&qid=1507823541&sr=8-1-spell&keywords=scalaa
Futureとは
- Scalaで非同期処理を扱うためのもの
- 処理をブロックすることなく、結果の値を処理、合成できる
- 非同期で行われる処理が
完了
すると成功
または失敗
が値で表現される
背景
- Javaで提供されているマルチスレッドによる制御は
共有データ
とロックモデル
で並行処理を実現する - ロックと共有モデルはデッドロックのリスクと戦ったりとなかなか難しいのだ
- 頑張って実装すれば共有データとロックを隠すことはできるだろうが‥
- 難しいというものの、
java.util.concurrent
を使えばスレッドを生で扱うよりは優しい - この難しさに対してScalaのFutureは
共有データ
とロック
についてあまり考えず並行処理を実現できる
背景を踏まえた上で
- 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 Future
とin 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
等が使えて非同期で渡って来る値を難しいことせずに扱える-
filter
,collect
...紹介しきれないほど便利な奴らがあるぞ! - 便利な奴らを紹介してくれる良記事 -> https://qiita.com/mtoyoshi/items/f68beb17710c3819697f
-
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.apply
がExecutionContext
としてimplicitで必要としている
ExecutionContext
とはなにか
- Futureに渡されたコードブロックを実行する仕組みを提供するもの
- 非同期であるというのは実装によるものでやろうと思えば同期的なFutureも作れちゃう
-
import scala.concurrent.ExecutionContext.Implicits.global
はScalaが提供しているExecutionContextの実装の一つ- Scala 2.12ではJavaのForkJoinPoolという実装を利用して実現しているらしい..