Luong Ba Linh
Luong Ba Linh

Reputation: 802

Asynchronous in Failure

Why is the user's name not printed when I comment out the println("testing")?

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

object Future3 extends App {
  val userFuture = Future(
  User("Me")
  )
  val userNameFuture: Future[String] = userFuture map {
    user => user.name
  }

  userNameFuture onSuccess {
    case userName => println(s"user's name =  $userName")
  }

  //  println("testing")
}

case class User(name: String)

Upvotes: 2

Views: 129

Answers (2)

Martin Senne
Martin Senne

Reputation: 6059

Short answer

The ExecutionContext.Implicits.global creates daemon threads. (see Scala source code scala.concurrent.impl.ExecutionContextImpl.DefaultThreadFactory) These are threads the JVM will not wait for on exit (in your case, when the main routine stops). Thus, before the userNameFuture which runs as a daemon thread has finished, the main routine is already finished and does not wait for the future threads to finish.

To prevent this from happening, either use non-daemon thread, e.g. to create such an implicit ExecutionContext

implicit val ec = (scala.concurrent.ExecutionContext.fromExecutorService(Executors.newCachedThreadPool()))

or use

Await.result( userNameFuture, Duration.Inf ) 

in the main routine.

Attention: If you use the latter approach with both Await.result and onSuccess callback, it still can happen, that the main routine exits first and no output of username will be made, as there is no order which of both happens first.

Long answer

Have a look at the code

object F2 {

  def main(args: Array[String]): Unit = {

    import scala.concurrent.ExecutionContext.Implicits.global
    import scala.util.Success

    val userFuture = Future {
      Thread.sleep(1000)
      println( "userFuture runs on: " + Thread.currentThread().getName)
      Thread.sleep(1000)
      User("Me")
    }

    val userNameFuture: Future[String] = userFuture map {
      user => {
        Thread.sleep(2000)
        println( "map runs on: " + Thread.currentThread().getName )
        Thread.sleep(2000)
        user.name
      }
    }

    val p = Promise[Boolean]()

    userNameFuture onSuccess {
      case userName => {
        println( "onSuccess runs on : " + Thread.currentThread().getName )
        println(s"user's name =  $userName")
        p.complete(Success(true))
      }
    }


    println( "main runs on: " + Thread.currentThread().getName )
    println( "main is waiting (for promise to complete) .... ")
    Await.result( p.future, Duration.Inf )
    println( "main got promise fulfilled")
    println( "main end ")

  }
}

whose output is

main runs on: run-main-b
main is waiting (for promise to complete) .... 
userFuture runs on: ForkJoinPool-1-worker-5
map runs on: ForkJoinPool-1-worker-5
onSuccess runs on : ForkJoinPool-1-worker-5
user's name =  Me
main got promise fulfilled
main end 

First, you can see, that both userFuture and it's map operation run on ForkJoinPool as daemon threads.

Second, main is run through first, printing "main is waiting for promise" and waits here (only for demenstration purposes) for the promise to be fulfilled. If main wouldn't wait here ( try yourself, by commenting out the Await) for the promise completion, the main routine would just print the other two lines and is done. As a result, the JVM would close (and you would never see output of onComplete)

Trick (for debugging) via SBT

In general, if you are using SBT and invoke program execution via run, then you can still see the output of daemon threads, as the JVM is not terminated, if started from within SBT. So, if you start via SBT run you are soon back to the SBT prompt (because main routine has finished), but output of threads (onComplete) is visible in SBT.

Upvotes: 0

ka4eli
ka4eli

Reputation: 5424

The reason is that default ExecutionContext global executes your future block on daemon thread and main thread doesn't wait for daemons to complete. You can use Thread.sleep(1000), Await.result(userNameFuture, 1 second) or another thread blocking operation in main thread to wait for some time so that your future's thread completes.

Another way is to run future on not-daemon thread:

import java.util.concurrent.Executors

import scala.concurrent.{ExecutionContext, Future}

object Future3 extends App {

  implicit val executor = ExecutionContext

    .fromExecutorService(Executors.newCachedThreadPool())  //not-daemon threads


  val userFuture = Future(
    User("Me")
  )
  val userNameFuture: Future[String] = userFuture map {
    user => user.name
  }

  userNameFuture onSuccess {
    case userName => println(s"user's name =  $userName")
  }

}

case class User(name: String)

Upvotes: 2

Related Questions