1. 概要

現代のプログラミングでは、アプリケーションは並行性と並列性をサポートする必要があります。 このチュートリアルでは、Scalaでの非同期プログラミングと、結果を同期的に処理する方法を見ていきます。

2. 将来

スレッドの操作は、特に大規模なアプリケーションを作成する場合、面倒になる可能性があります。 この問題を軽減するために、Scalaは Future API を提供して、コードを簡素化し、機能的な非同期抽象化を提供します。

Future は、まだ存在していない可能性のある値のプレースホルダーオブジェクトとして機能します。 これにより、計算を同時に実行する簡単な方法が得られます。 Futureは作成と同時に実行を開始し、futureのある時点で結果を返します

Futureオブジェクトでapplyメソッドを呼び出すことにより、Futureを作成できます。 URLからコンテンツを取得したいとします。

def fetchDataFrom(url : String, waitTime : Long = 0L) : Future[String] = Future {
  Thread.sleep(waitTime)
  Source.fromURL(url).getLines().mkString
}
fetchDataFrom("https://www.baeldung.com")

ただし、これによりコンパイラからエラーが返されます。

Cannot find an implicit ExecutionContext. You might pass an (implicit ec: ExecutionContext) parameter to your method
or import scala.concurrent.ExecutionContext.Implicits.global.
  Future {

コンパイラは、使用可能な実行コンテキストがないことを通知します。 Future は別のスレッドで実行され、別のスレッドで実行する機能を提供する抽象化は、私たちが提供しなかった ExecutionContext、です。

この例を正常に実行するには、scala.concurrent.ExecututionContext.Implicits.globalをインポートする必要があります。

3. 将来完了

Future は別のスレッドで実行されるため、Futureを作成したスレッドは自由に実行を続行できます。 前の例では、メインスレッドで行う作業がなくなったためにプログラムが終了しましたが、別のスレッドで実行されたFutureが完了していない可能性があります。

Future の結果は、 Try、であり、計算が成功または失敗のいずれかとして表されます。

では、どのようにして結果を抽出しますか未来? これを行う1つの方法は、メインスレッドを追加してシャットダウンしないようにすることです。 Thread.sleep 電話:

fetchDataFrom("https://www.baeldung.com").onSuccess(result => println(result))

Thread.sleep(1000)

ただし、 Thread.sleep()、を使用することは、応答時間が常にわかるとは限らないため、理想的なソリューションではありません。

では、 Future が完了するまで待機し、その後のみ実行を続行するようにメインスレッドに指示できる方法があるとしたらどうでしょうか。 ここで、 Await APIが登場します。

3.1. Wait.ready

Await クラスには、 Future が完了するかタイムアウトになるまで、呼び出し元のスレッドをブロックするreadyメソッドが含まれています。

readyメソッドをこの例に適用してみましょう。

val fut = fetchDataFrom("https://www.baeldung.com")
fut.isCompleted shouldBe false

val completedFuture: Future[String] = Await.ready(fut, 2 seconds)

fut shouldBe completedFuture
completedFuture.isCompleted shouldBe true
completedFuture.isInstanceOf[Future[String]] shouldBe true
val assertion = completedFuture.value match {
 case Some(result) => result.isSuccess
 case _ => false
}
assertion shouldBe true

Await.readyは2つのパラメーターを取ります。1つは待機したいFuture で、もう1つは最大期間です。 これはThread.sleepのように機能すると想定できます。この場合、指定された時間が経過した後にのみ、呼び出し元のスレッドがウェイクアップします。 ただし、そうではありません Await.ready。 

Await.ready に提供される期間は、呼び出し元のスレッドが待機できる最大時間を定義し、 Future がその時間枠内に完了しない場合、[Xをスローします。 X205X]java.util.concurrent.TimeoutException。

Await.readyは、元の完了したFutureをSuccessまたは Failureとして返します。次に、値を安全に使用または抽出するのはコードの呼び出し元です。

3.2. Await.result

Await.result は、 Future が完了するかタイムアウトになるまで呼び出しスレッドをブロックするという点で、Await.readyに似ています。 主な違いは、完成した Future を返すのではなく、Futureの結果から値を抽出しようとすることです。

resultメソッドを適用するとどうなるか見てみましょう。

val fut = fetchDataFrom("https://www.baeldung.com")
fut.isCompleted shouldBe false
val completedFutureResult: String = Await.result(fut, 2 seconds)
completedFutureResult.isInstanceOf[String] shouldBe true

結果は次のようになることに注意することが重要です。の代わりに Future[文字列] の場合のように Await.ready。 

さらに、Await.resultAwait.readyで表すことができます。

val result: String = Await.ready(fut, 5 seconds).value.get.get

Await.result を使用する方が、 Await.ready を使用するよりも簡単な代替手段になると思われるかもしれませんが、Futureから値を直接抽出するアプローチはそうではありません。 Future は、 Failure でラップされた例外を除いて完了する可能性があるため、お勧めします。

例外のときにこの値を抽出しようとすると、プログラムがクラッシュします。

4. Await.result対。 Wait.ready

最大で指定された期間の両方のAPIのブロック。 ただし、 Await.result は、 Future の結果をできるだけ早く返そうとし、Await中にFuture が例外で失敗した場合、例外をスローします。 ready は、完了した Future を返します。この結果から、結果(SuccessまたはFailure)を安全に抽出できます。

違いを詳しく見てみましょう。

def futureWithoutException(): Future[String] = Future {
  "Hello"
}
def futureWithException(): Future[String] = Future {
  throw new NullPointerException()
}

ここでは、2つの Futureが非同期でStringを返すことが期待されています。後者は失敗し、前者は成功します。 Await.readyを使用する場合とAwait.resultを使用する場合の、これらのFutureの処理にプログラムがどのように応答するかを見てみましょう。

val f1 = Await.ready(futureWithoutException, 2 seconds)
assert(f1.isInstanceOf[Future[String]] && f1.value.get.get.contains("Hello"))
val f2 =  Await.ready(futureWithException, 2 seconds)
assert(f2.isInstanceOf[Future[String]] && f2.value.get.failed.get.isInstanceOf[NullPointerException])
val f3 = Await.result(futureWithoutException, 2 seconds)
assert(f3.isInstanceOf[String] && f3.contains("Hello"))
assert (intercept[Exception] { Await.result(futureWithException, 2 seconds)}.isInstanceOf[NullPointerException]) 

上記の例では、 Await.result を使用すると、プログラムが NullPointerException から値を抽出しようとするため、NullPointerExceptionがスローされることがはっきりとわかります。 ]に包まれた 失敗。 

Await.result を使用する際のもう1つの注意点は、計算が実際に失敗したのか、コードでタイムアウトしたのかを判断するのが難しいことです。

経験則として、非常に必要な場合を除いて、Awaitの使用は避けることをお勧めします。

5. 結論

この記事では、Scalaを使用して非同期プログラムを作成する方法と、結果を処理するさまざまな方法について説明しました。 また、 Future を同期的に処理する方法の違いについて説明し、両方がFutureを処理する方法についての優れた直感を開発しました。 いつものように、ソースコードはGitHubにあります。