I am very new to Scala and following the Scala Book Concurrency section (from docs.scala-lang.org). Based off of the example they give in the book, I wrote a very simple code block to test using Futures:
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
object Main {
def main(args: Array[String]): Unit = {
val a = Future{Thread.sleep(10*100); 42}
a.onComplete {
case Success(x) => println(a)
case Failure(e) => e.printStackTrace
}
Thread.sleep(5000)
}
}
When compiled and run, this properly prints out:
Future(Success(42))
to the console. I'm having trouble wrapping my head around why the Thread.sleep()
call comes after the onComplete callback method. Intuitively, at least to me, would be calling Thread.sleep()
before the callback so by the time the main thread gets to the onComplete
method a
is assigned a value. If I move the Thread.sleep()
call to before a.onComplete
, nothing prints to the console. I'm probably overthinking this but any help clarifying would be greatly appreciated.
CodePudding user response:
When you use the Thread.sleep()
after registering the callback
a.onComplete {
case Success(x) => println(a)
case Failure(e) => e.printStackTrace
}
Thread.sleep(5000)
then the thread that is executing the body of the future has the time to sleep one second and to set the 42
as the result of successful future execution. By that time (after approx. 1 second), the onComplete
callback is already registered, so the thread calls this as well, and you see the output on the console.
The sequence is essentially:
- t = 0: Daemon thread begins the computation of
42
- t = 0: Main thread creates and registers callback.
- t = 1: Daemon thread finishes the computation of
42
- t = 1 eps: Daemon thread finds the registered callback and invokes it with the result
Success(42)
. - t = 5: Main thread terminates
- t = 5 eps: program is stopped.
(I'm using eps
informally as a placeholder for some reasonably small time interval; eps
means "almost immediately thereafter".)
If you swap the a.onComplete
and the outer Thread.sleep
as in
Thread.sleep(5000)
a.onComplete {
case Success(x) => println(a)
case Failure(e) => e.printStackTrace
}
then the thread that is executing the body of the future will compute the result 42
after one second, but it would not see any registered callbacks (it would have to wait four more seconds until the callback is created and registered on the main thread). But once 5 seconds have passed, the main thread registers the callback and exits immediately. Even though by that time it has the chance to know that the result 42
has already been computed, the main thread does not attempt to execute the callback, because it's none of its business (that's what the threads in the execution context are for). So, right after registering the callback, the main thread exits immediately. With it, all the daemon threads in the thread pool are killed, and the program exits, so that you don't see anything in the console.
The usual sequence of events is roughly this:
- t = 0: Daemon thread begins the computation of
42
- t = 1: Daemon thread finishes the computation of
42
, but cannot do anything with it. - t = 5: Main thread creates and registers the callback
- t = 5 eps: Main thread terminates, daemon thread is killed, program is stopped.
so that there is (almost) no time when the daemon thread could wake up, find the callback, and invoke it.
CodePudding user response:
A lot of things in Scala are functions and don't necessarily look like it. The argument to onComplete
is one of those things. What you've written is
a.onComplete {
case Success(x) => println(a)
case Failure(e) => e.printStackTrace
}
What that translates to after all the Scala magic is effectively (modulo PartialFunction
shenanigans)
a.onComplete({ value =>
value match {
case Success(x) => println(a)
case Failure(e) => e.printStackTrace
}
})
onComplete
isn't actually doing any work. It's just setting up a function that will be called at a later date. So we want to do that as soon as we can, and the Scala scheduler (specifically, the ExecutionContext
) will invoke our callback at its convenience.