Hey folks, in this blog we will be discussing and try to gather some knowledge on how multithreading, concurrency can be achieved and asynchronous computations can be done on immutable values by the use of Futures.
Why do we need Futures?
The concept of Futures was introduced to allow concurrent programming and avoid wastage of resources which will be blocked in case of sequential code. It allows the computation of immutable values in an asynchronous manner i.e. in a non-blocking manner.
What is a Future?
A Future represents a value that will be available in the future or the exception that occurred while evaluating that value. A Future is a concurrency abstraction that represents a future value and comes with a very powerful and convenient API that lets you deal with that future result in a type-safe and high-level manner. A Future can be in three possible states: it can either be scheduled/running, failed, or successful.
Whenever a Future operation is created, Scala creates a new thread to execute the Future’s code.
How to use Futures?
To use Scala Futures, we require an Execution Context which executes the Future and this acts as a thread pool. Execution Context is provided implicitly to the code running in Future.
We require the following two imports to execute a future:
import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global
Creating a Future in Scala is easy, we need to simply enclose a block of code in the Future. This can be seen in the following example:
scala> def sum(x: Int, y: Int) = Future { x + y } sum: (x: Int, y: Int)scala.concurrent.Future[Int]
The above example creates a future called sum which is of type Future[Int] and this function will get executed in a separate thread when it is called.
Callback mechanism
You require a callback mechanism for a future to process the result of the future obtained after execution. We need to write callback functions which execute on the basis of success or failure of Future’s execution. Following callback functions can be used :
onComplete: It takes a callback function of type Try[T] => U i.e. the future gets completed, either with a successful result or failure.
onSuccess: This method is called when the future gets completed with a successful result. It takes a partial function.
onFailure: This method is called when the future gets completed with a failure. It also takes a partial function.
All the above callback functions return Unit and so are avoided for use. It is not necessary that the callback function will be executed on the same thread or a different thread and there is no specific order in their execution.
For example,
scala> sum(9,8).onComplete{ case Success(result) => print(result) case Failure(ex) => print(ex) } 17
To use the above code, include the following imports:
import scala.util.Success and import scala.util.Failure
Further many combinators can be used with Futures to use the result obtained after the execution of Future. For example, map, flatmap and foreach (used to process the successful result of futures), recover, recoverWith and fallBackTo (used to handle failure in futures).
Also, for comprehension can be used to combine multiple futures which run independently which makes the code a lot cleaner and optimized. To use for comprehension, we must first declare the individual futures, then combine them using a for-yield construct and then process the final result of all the futures. For example,
scala> val f1 = Future { Thread.sleep(800); 1 } f1: scala.concurrent.Future[Int] = Future() scala> val f2 = Future { Thread.sleep(200); 2 } f2: scala.concurrent.Future[Int] = Future() scala> val f3 = Future { Thread.sleep(400); 3 } f3: scala.concurrent.Future[Int] = Future() scala> val result = for { | r1 <- f1 | r2 <- f2 | r3 <- f3 | } yield (r1 + r2 + r3) result: scala.concurrent.Future[Int] = Future() scala> result.onComplete { | case Success(result) => print(result) | case Failure(ex) => print(ex) | } 6
Conclusion
Multithreading code is difficult to write but with the help of immutability in functional programming and with the use of Scala Future libraries, it has become a lot easier. They provide mechanisms to handle failures, combine futures, process results of futures in an easy manner.
Hope this blog is helpful in writing non-blocking concurrent code.
Thanks for reading!