1. 概要

FuturePromiseは、並行プログラミングの2つの高レベル非同期構造です。 このチュートリアルでは、Scalaプログラミング言語でそれらとそれぞれの目的について詳しく学びます。

2. 将来

Future は、進行中の計算の結果の読み取り専用プレースホルダーです。 これは、まだ存在していない実際の値のプロキシとして機能します。 IOバウンドまたはCPUバウンドの操作を考えてみてください。これらの操作は、完了するまでにかなりの時間がかかります。

2.1. Futureの作成

非同期計算を作成するには、計算をFutureの適用関数内に配置します。

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

val result = Future {
  println("Long running computation started.")
  val result = {
    Thread.sleep(5000)
    5
  }
  println("Our computation, finally finished.")
  result
}

将来を実行するには、ExecutionContext が必要です。これにより、ビジネスロジック(コード)を実行環境から分離できます。 ExecutionContext は暗黙的なパラメーターであるため、 ExecutionContext をインポートまたは作成し、スコープ内でimplicitとしてマークすることができます。 ここでは簡単にするために、グローバル実行コンテキストを使用します。

implicit val ec = scala.concurrent.ExecutionContext.Implicits.global
val r1 = Future{ /* computation */ }(ec) //Passing ec explicitly
val r2 = Future{ /* computation */ }     //Passing ec implicitly

2.2. Futureの作成

Future を使用する利点は何ですか? FutureにはmapおよびflatMap操作があるため、慣用的なfor-comprehensionsを使用して未来を連鎖させることができます。 これは私たちに良いものを与えます並行プログラミングにおける構成可能性。 

の中にユーザー以下のクラス、 ユーザーを作成() 関数は、次のような他の非同期関数の結果に依存します存在しない()アバター()。 これらの計算を一緒に構成する方法を見てみましょう。

import java.math.BigInteger
import java.net.URL
import java.security.MessageDigest

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

type Name = String
type Email = String
type Password = String
type Avatar = URL

case class User(name: Name, email: Email, password: Password, avatar: Avatar)

def notExist(email: Email): Future[Boolean] = Future {
  Thread.sleep(100)
  true
}
def md5hash(str: String): String =
  new BigInteger(1,
    MessageDigest
      .getInstance("MD5")
      .digest(str.getBytes)
  ).toString(16)
def avatar(email: Email): Future[Avatar] = Future {
  Thread.sleep(200)
  new Avatar("http://avatar.example.com/user/23k520f23f4.png")
}
def createUser(name: Name, email: Email, password: Password): Future[User] =
  for {
    _ <- notExist(email)
    avatar <- avatar(email)
    hashedPassword = md5hash(password)
  } yield User(name, email, hashedPassword, avatar)

ここで、 createUser()は名前、電子メール、およびパスワードを受け取り、対応する User Future [User]を含むFutureを返します。 )。

新しいユーザーを作成するには、IO操作を含むいくつかの手順を実行する必要があります。 1つ目は、データベースに対応するユーザーがいるかどうかを確認することです。 2つ目は、パブリックアバターサービスからアバターを取得することです。

Thread.sleep()を使用して、データベース操作のレイテンシーをシミュレートしています。

avatar() notExist() Future であり、すべてのFutureにはflatMapがあるため、次のことができます。それらを理解のためのスタイルで構成します。

2.3. 値へのアクセス

これまで、 Future を作成してチェーン化する方法を学びました。そこで、 Futureの値の結果にアクセスするにはどうすればよいですか?値へのアクセスの詳細に進む前に Future について、Futureコンストラクト値の状態について詳しく見ていきましょう。

Futureには2つの段階があります。

  1. 未完了:計算はまだ完了していません。
  2. 完了:計算が完了し、結果は2つの状態のいずれかになります。 計算の結果が値の場合は成功と見なされ、例外の場合は失敗と見なされます。

進行中の計算の結果が準備できるまで、 Future の状態は完了せず、その後、Futuresuccessまたはのいずれかになります。 X178X]障害状態。

ここでは、Futureの結果を取得するための2つの方法を紹介します。

  1. onCompleteコールバックの使用
  2. result()または ready ()を使用してFutureをブロックする

FutureにはonComplete()メソッドがあります。 f:Try [T] => Uをコールバックとして取得します。これにより、完了段階の各状態で何をするかを決定できます。

val userFuture: Future[User] =
  createUser("John", "[email protected]", "secret")

userFuture.onComplete {
  case Success(user) =>
    println(s"User created: $user")
  case Failure(exception) =>
    println(s"Creating user failed due to the exception: $exception")
}

onComplete()メソッドにコールバックを提供することにより、 Future が完了すると、コールバックメソッドが呼び出されます。

非同期計算の結果にアクセスする別の方法は、それをブロックすることですが、これはお勧めしません。 Await.result()および Await.ready()関数は、計算が完了するまでスレッドをブロックします。 それぞれの署名は次のとおりです。

def result[T](awaitable: Awaitable[T], atMost: Duration): T
def ready[T](awaitable: Awaitable[T], atMost: Duration): awaitable.type

最初の関数パラメーターは、待機するawaitableです。 すべてのFuture待機可能なので、ここで渡すことができます。 次のパラメータはdurationで、計算が完了するのを待ちます。

Awaitresult()メソッドを実行すると、Futureの値を取得できます。

val user: User = Await.result(userFuture, Duration.Inf)

result()メソッドを使用する場合は、完了する前に例外をスローする可能性があるため、注意してください。

別の方法は、 Future にready()関数を適用して、Futureの値を抽出せずに計算が完了するまで待つことです。

val completedFuture: Future[User] = Await.ready(userFuture, Duration.Inf)

‌その後、 value()メソッドが Future で呼び出されると、結果は None ではなく、 Some( Success(t))または Some(Failure(error))

completedFuture.value.get match {
  case Success(user) =>
    println(s"User created: $user")
  case Failure(exception) =>
    println(s"Creating user failed due to the exception: $exception")
}

3. 約束

Promiseは、Futureを完成させる書き込み可能な単一割り当てコンテナーです。 PromiseFutureに似ています。 ただし、 Future は非同期操作の読み取り側に関するものであり、Promiseは書き込み側に関するものです。

言い換えると、 Promiseは、将来のある時点で使用可能になる値への書き込みハンドルです。 完了した非同期操作の値をFuture、に入れ、 success を呼び出すことにより、Futureの状態を未完了から完了に変更できます。方法。 Promiseが完了すると、success()メソッドを再度呼び出すことはできません。

それでは、PromiseからFutureを作成するために必要な手順を見てみましょう。

  1. 将来返したいタイプのPromiseを作成します。
  2. ExecutionContext でexecuteメソッドを呼び出して、ExecutionContextで計算コードのブロックを実行します。 実行が成功すると、 Promisesuccess()メソッドを呼び出して、完了した計算の値を設定します。 計算が失敗した場合は、 failure()メソッドを呼び出して、Promiseの状態をfailureに変更します。
  3. Future を返します。これには、Promiseの値が含まれます。

block:=> T を計算ブロックとして取得し、futureの値を含むFuture[T]を返す関数を作成してみましょう。 Promise

def runByPromise[T](block: => T)(implicit ec: ExecutionContext): Future[T] = {
  val p = Promise[T]
  ec.execute { () =>
    try { 
      p.success(block)
    } catch {
      case NonFatal(e) => p.failure(e)
    }
  }
  p.future
}

Promise は、レガシーJavaコールバックベースのAPIとFuturesの間のギャップを埋めるための優れた構成です。

4. 結論

この記事では、Scalaでノンブロッキングの非同期コードを記述するための2つの構成を示しました。 非同期操作の読み取り側を処理するために、 Future 構造を導入し、書き込み側を処理するために、Promiseを導入しました。

いつものように、完全なソースコードはGitHubにあります。