Future and Concurrency

Reading Time: 6 minutes

Before moving to the future, let’s first understand what is concurrency.

Concurrency:

 Concurrency is that we are going to perform more than one task at the same time. The rapid increase in the number of core-processor increase attracts in concurrency.

Concurrency is not like the parallelism. Concurrency means multiple tasks which start, run, and complete in overlapping time periods, in no specific order. Parallelism is when multiple tasks OR several parts of a unique task literally run at the same time.

Java provides the concurrency through the locking and shared memory. But still, this approach is hard to get into practice. So scala provide an alternative approach to avoid these difficulties by an asynchronous transformation of immutable state: FUTURE

The best example for asynchronous is: Life

Concurrency and future are correlated to each other.Life is asynchronous and we don’t like to wait at all. We are very expert in context switching rather than waiting for something.

Future:

Form Concurrency and future, to achieve the concurrency Future provides asynchronous programming..The future is an efficient way to perform many operations at the same time in an efficient and non-blocking way. The future is a placeholder object which holds the value of an operation that may not exist yet. The most important thing is that the future is not an operation itself.

It is only a read-only container. And we can’t write in it.

Suppose Future[T] -> the value of T might become available at some time and if something goes wrong the future will go throwable.

As uptil now we study that result provided by the Future is asynchronous we often need to notify when the result is actually complete. This implemented using a callback. The future provides some callback so that you can work with that value. Most of them used are map and Flat map. 

 We see the different callbacks in detail later.

Future Stages and their values

The future is impatient, As you create a future and computation that you passed to the future will be scheduled to run at that time. The future also takes a by-name parameter, which means parameters that are not computed until you used it.

But when we want to run a simple example using Future it shows an error

scala> import scala.concurrent.Future
import scala.concurrent.Future

scala> val a=5
a: Int = 5

scala> val b=6
b: Int = 6

scala> val c=Future(a+b)
                   ^
       error: Cannot find an implicit ExecutionContext. You might pass
       an (implicit ec: ExecutionContext) parameter to your method.

It means that we need an execution context to run the future.

Execution Context:

It is a trait that extends to AnyRef. Execution context is used to perform operations asynchronously which are to a method.

It is also used to configure how and on which thread pool the future will run. So the selection of a specific execution context is important. If you are not defining any execution context then by default it works on the global execution context. Which uses the Fork-Join thread pool.

APIs such as Future.onComplete require you to provide a callback and an implicit ExecutionContext. The implicit ExecutionContext will be used to execute the callback. 

The default execution context will work on a fixed number of threads.

We can simply import the execution context using “import scala.concurrent.ExecutionContext.Implicits.global”.

Then also developers have to focus on where he/she wants to set the execution policy and which one. Developers can set the execution context in a particular method and can be defined as a class parameter.

 The recommended approach is to add (implicit ec: ExecutionContext) to methods, or class constructor parameters, which need an ExecutionContext.

Then locally import a specific ExecutionContext in one place for the entire application or module, passing it implicitly to individual methods. Alternatively, define a local implicit val with the required ExecutionContext.

Creating Future:

The simplest way to create a future is to invoke a Future. apply method which makes the computation in an asynchronous way and returns the result in the Future.

Example:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object Addition extends App {
 val firstNum = 5
 val secondNum = 6
 val addition = Future {
   Thread.sleep(500)
   firstNum + secondNum
 }
 println(addition.isCompleted)
 println(addition.value)
}

The above example completes the value 11. Future provides two different methods

  • isComplete: use to check whether future is completed or not, if completes providing true otherwise false
  • value: if the future is incomplete then it returns none, if completes then it will return Some(value)
knoldus@knoldus-Vostro-3559:~/IdeaProjects/future$ sbt run
[info] Loading global plugins from /home/knoldus/.sbt/1.0/plugins
[info] Loading project definition from /home/knoldus/IdeaProjects/future/project
[info] Loading settings for project future from build.sbt ...
[info] Set current project to future (in build file:/home/knoldus/IdeaProjects/future/)
[info] Compiling 1 Scala source to /home/knoldus/IdeaProjects/future/target/scala-2.13/classes ...
[info] running Addition 
false
None
knoldus@knoldus-Vostro-3559:~/IdeaProjects/future$ sbt run
[info] Loading global plugins from /home/knoldus/.sbt/1.0/plugins
[info] Loading project definition from /home/knoldus/IdeaProjects/future/project
[info] Loading settings for project future from build.sbt ...
[info] Set current project to future (in build file:/home/knoldus/IdeaProjects/future/)
[info] Compiling 1 Scala source to /home/knoldus/IdeaProjects/future/target/scala-2.13/classes ...
[info] running Addition 
true
Some(success(11))

Callbacks:

 By default future and promises are non-blocking and make the use of callback instead of blocking operators. They are also known as combinators. until now we see how to get the result in the future, now we see how this result becomes useful for other computations. In Scala, the future allows transformation on a result of the future to obtain a new future. 

A better approach to working with a future is to use its callback methods. There are three callback methods: onComplete, onSuccess, and onFailure. The following example demonstrates onComplete:

import scala.concurrent.{Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
object Addition extends App {
 val firstNum = 5
 val secondNum = 6
 val addition = Future {

   firstNum + secondNum
 }
 Thread.sleep(100)
 addition.onComplete {
   case Success(value) => println("addition=" + value)
   case Failure(e)     => println("not complete yet")
 }
}

The addition.onComplete method call sets up the callback. Whenever the Future completes, it makes a callback to onComplete, at which time that code will be executed.

  1. If the future is complete case Success will get execute.
  2. If the future is incomplete case failure will get execute.
  1. Transforming the future with the map: the map callback is use to map the result of the original future that addition shows in the below example to calculate the new result i.e subtraction which gives the result in a new future.
object Addition extends App {
 val firstNum = 5
 val secondNum = 6
 val addition = Future {
   firstNum + secondNum
 }
 addition.onComplete {
   case Success(sum) =>
     addition.map { sum =>
       println("subtraction="+(sum - 3))
     }
   case Failure(e) => println("not yet completed")
 }

}

2. Transforming the future with for expression: Scala’s future also declares a flatMap method, you can transform futures using a for expression.

import scala.concurrent.{Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}

object Addition extends App {
 val firstNum = 5
 val secondNum = 6

 val addition = Future {
   Thread.sleep(10)
   firstNum + secondNum
 }
 val subtraction = Future {
   Thread.sleep(10)
   10 - 5
 }

 val multiplication = {
   for {
     x <- addition
     y <- subtraction
   } yield (x * y)
 }
 multiplication.onComplete {
   case Success(result) => println("multiplication=" + result)

   case Failure(e) => println("not yet completed")
 }

 Thread.sleep(3000)
}

3. Filter and collect method: The filter method is use to filter the future result based on a particular condition, we can also say that it is use to validate the result. Where the collect method is used to validate the future result and transform it into a new operation 

object Addition extends App {
val addition = Future {
   Thread.sleep(10)
   5 + 6
 }
 val subtraction = Future {
   Thread.sleep(10)
   10 - 5
 }

 val multiplication = {
   for {
     x <- addition
     y <- subtraction
   } yield (x * y)
 }
 val valid = multiplication.filter(result => result > 20)

/*
val valid = multiplication.collect {
 case result if result > 20 => result + 5
}

valid.onComplete {
 case Success(result) =>
   println(
     s"multiplication $multiplication becomes $result which is greater than 20 "
   )
 case Failure(e) => println("not yet completed")
}

*/
valid.onComplete {
   case Success(result) =>
     println(s"multiplication $result greater than 20 ")
case Failure(e) => println("not yet completed")
 }
Thread.sleep(3000)
}

If you want to learn in more detail more with an example, you can Click Here

Promise:

The most common way to create a future is to use a Promise. Given a promise you can obtain a future that is controll by the promise. Promise is a counterpart of the future It is writable and single assignment container. The future will complete when you complete the promise.   Here’s an example:

scala> import scala.concurrent.Promise
import scala.concurrent.Promise

scala> val pro = Promise[String]
pro: scala.concurrent.Promise[String] = Future(<not completed>)

scala> val fue = pro.future
fue: scala.concurrent.Future[String] = Future(<not completed>)

scala> pro.success(“Hello Promise”)
res0: pro.type = Future(Success(Hello Promise))

To use the promise we need to first import the scala.concurrent.Promise. Before calling the success method on the promise if we try to access the value of future it gives none

scala> fut.value
res8: Option[scala.util.Try[Int]] = None

So we need to first complete the promise to access the value of the future using the success method. We can also use the failure method on a promise to fail it.

scala> fut.value
res10: Option[scala.util.Try[Int]] = Some(Success(42))

References: