Home > OS >  How can I have 3 Futures run asynchronously and display each answer?
How can I have 3 Futures run asynchronously and display each answer?

Time:10-20

I'm learning scala and I want to run 3 futures asynchronously and get an answer for each one. But I can't get it to work.

import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.Future
import scala.util.{Success, Failure}
import scala.concurrent.ExecutionContext.Implicits.global
def f_Fail(): Future[String] = Future {
      Thread.sleep(1000)
      throw new RuntimeException("わざと失敗")
    }
def f_success(): Future[String] = Future {
      Thread.sleep(1000)
      "future!"
    }
    val f1 = f_success()
    val f2 = f_success()
    val f3 = f_Fail()
    val future =for {
        n <- f1
        m <- f2
        p <- f3
    } yield n "\n" m "\n" p
Await.ready(future,Duration.Inf).value.get match {
      case Success(v) => println(v)
      case Failure(e) => println("not found")
    }

Expected results

"future!"

"future!"

"not found"

Actual result

"not found"

CodePudding user response:

A for-comprehension is so-called "syntactic sugar" for nested flatMap calls and finally a map. In your case, the for-comprehension can be effectively re-written as:

val future = f1.flatMap(n => f2.flatMap(m => f3.map(p => n "\n" m "\n" p)))

In the context of Futures, the flatMap method requires the Future upon which its called to be completed before the result is available for the following step. Simplifying for clarity, consider the following:

f1.flatMap(n => f2.map(m => m   n))

In order for the lamdba passed to the flatMap method to be invoked, the result of the calculation must be known.

Effectively, flatMap calls represent the concept of a direct dependency on a previous computation being executed successfully. In the same way, its "sugared" counterpart, the for-comprehension, represents the same.

This means that if any step of the chain fails, the overall result will be failed, which is what you are experiencing in your example.

What you can do is the following:

val f1 = f_success().recover(_ => "not found")
val f2 = f_success().recover(_ => "not found")
val f3 = f_Fail().recover(_ => "not found")

for {
  n <- f1
  m <- f2
  p <- f3
} {
  println(n)
  println(m)
  println(p)
}

Notice that:

  • Futures are "eager", i.e. the computation is started immediately when the Future itself is instantiated. This means that the first three lines effectively start each computation independently and (given enough threads and resources) concurrently or even in parallel.
  • each Future defines how to be recovered by returning the "not found" string in case of error (which is the case for f3).
  • the side effect (println) is performed as part of the for-comprehension itself, allowing you to avoid blocking on the synchronous context -- in this case, the for-comprehension without yield is equivalent to executing the final step inside a foreach instead of a map, which is better suited to express side effects.

Now, let's say that you want to start your Futures lazily by simply wrapping their definition in a function. You'll notice that suddenly the execution doesn't necessarily take advantage of multiple threads and cores and takes 3 seconds instead of one, as in the following example:

def f1 = f_success().recover(_ => "not found")
def f2 = f_success().recover(_ => "not found")
def f3 = f_Fail().recover(_ => "not found")

for {
  n <- f1
  m <- f2
  p <- f3
} {
  println(n)
  println(m)
  println(p)
}

This is because of the semantics of flatMap. You can solve this problem by using a construct that doesn't imply some form of direct dependency between the steps of the calculation, like zip:

def f1 = f_success().recover(_ => "not found")
def f2 = f_success().recover(_ => "not found")
def f3 = f_Fail().recover(_ => "not found")

for (((n, m), p) <- f1.zip(f2).zip(f3)) {
  println(n)
  println(m)
  println(p)
}

This runs again in ~1 second as you might expect.

Alternatively, if you want to still return the result as a Future, you can of course use yield as follows:

val future = for (((n, m), p) <- f1.zip(f2).zip(f3)) yield s"$n\n$m\n$p"

You can read more about for-comprehensions here on the Scala book.

  • Related