Search code examples
scalafutureexecutioncontext

Scala Future blocks transformations?


I have the following test code snippet:

import scala.concurrent.{Await, Future}
import scala.concurrent.duration.Duration
import scala.util.Success

import scala.concurrent.ExecutionContext.Implicits.global

object FutureAndThen extends App {

  val future = Future {
    println("Started initial Future")
    10
  } andThen { case Success(value) =>
    println("Started callback")
    Thread.sleep(5000)
    println(s"Finished callback: value = $value")
  } map { x =>
    println("Chained transformation")
    x * 2
  }

  println(Await.result(future, Duration.Inf))

}

It produces the following output:

Started initial Future
Started callback
Finished callback: value = 10
Chained transformation
20

I expect andThen callback to be executed asynchronously. But the actual execution is the next:

  1. Execute original future
  2. Execute asynchronous callback
  3. Run transformations (map)

At first I thought that the issue is in ExecutionContext which decided to run all these operations in single thread. And I changed this to use custom ExecutionContext:

implicit val ctx = ExecutionContext.fromExecutor(
  (command: Runnable) => new Thread(command).start()
)

And the result is the same. Could you advice me what I am missing?


Solution

  • This behavior is actually documented for Future.andThen:

    Applies the side-effecting function to the result of this future, and returns a new future with the result of this future.

    This method allows one to enforce that the callbacks are executed in a specified order.

    It means that map will not start its job before the calculations inside andThen are completed. If this is not what you want, you have to call map on the original Future. And then you can use onComplete instead of andThen, so code would become something like this:

      val future = Future {
        println("Started initial Future")
        10
      }
    
      future onComplete { case Success(value) =>
        println("Started callback")
        Thread.sleep(2000)
        println(s"Finished callback: value = $value")
      }
    
      val f2 = future map { x =>
        println("Chained transformation")
        x * 2
      }
    
      println(Await.result(f2, Duration.Inf))
    

    P.S. AFAIK there is no standard onComplete equivalent that can be used with method chaining and I think this is by design to make it easier to predict behavior by reading the code. Currently you can use a simple rule: if it is chained - it is executed later.