What's the difference between Future(blocking(blockingCall()))
and blocking(Future(blockingCall()))
? Both of these are defined in scala.concurrent._
I've looked at the scala docs and some other stack overflow answers but remain unclear on what the difference is.
blocking
acts as a hint to the ExecutionContext
that it contains blocking code, so that it may spawn a new thread to prevent deadlocks. This presumes the ExecutionContext
can do that, but not all are made to.
Let's look at each one-by-one.
Future(blocking(blockingCall()))
This requires an implicit ExecutionContext
to execute the Future
. If the ExecutionContext
being used is a BlockContext
(like scala.concurrent.ExecutionContext.Implicits.global
is), it may be able to spawn a new thread in its thread pool to handle the blocking call, if it needs to. If it isn't, then nothing special happens.
blocking(Future(blockingCall()))
This tells us that Future(blockingCall())
may be a blocking call, so it is treated the same as above. Except here, Future.apply
is non-blocking, so using blocking
effectively does nothing but add a little overhead. It doesn't matter what ExecutionContext
we're calling it from here, as it isn't blocking anyway. However, the blocking call within the Future
will block a thread in the ExecutionContext
it's running on, without the hint that its blocking. So, there is no reason to ever do this.
I've explained blocking
more in depth in this answer.
REPL Examples:
import java.util.concurrent.Executors
import scala.concurrent._
val ec = scala.concurrent.ExecutionContext.Implicits.global
val executorService = Executors.newFixedThreadPool(4)
val ec2 = ExecutionContext.fromExecutorService(executorService)
def blockingCall(i: Int): Unit = { Thread.sleep(1000); println("blocking call.. " + i) }
// Spawns enough new threads in `ec` to handle the 100 blocking calls
(0 to 100) foreach { i => Future(blocking(blockingCall(i)))(ec) }
// Does not spawn new threads, and `ec2` reaches thread starvation
// execution will be staggered as threads are freed
(0 to 100) foreach { i => Future(blocking(blockingCall(i)))(ec2) }
// `blocking` does nothing because the `Future` is executed in a different context,
// and `ec2` reaches thread starvation
(0 to 100) foreach { i => blocking(Future(blockingCall(i))(ec2)) }
// `blocking` still does nothing, but `ec` does not know to spawn new threads (even though it could)
// so we reach thread starvation again
(0 to 100) foreach { i => blocking(Future(blockingCall(i))(ec)) }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With