Scala Futures - Concurrency Interpreted!
Tags: Parallel Computing, Functional Programming, Distributed Systems, Scala, Concurrency

if you are from Java background , you might be aware of java.util.concurrent.Future yes?

there are several challenges in using this,

1- threads are always blocked while retrieving values.

2- wait time for the completion of computation.

3- get method is the only way to retrieve values.

Weary and antagonistic way of writing concurrent code.

we have Future’s in Scala(Better one’s)-

scala.concurrent.Future

with Scala Futures we can achieve

1- real-time non-blocking computation.

2- callbacks for onComplete(Success/Failure) i,e- values in future are instances of Try clause,

3- map multiple future’s. futures are immutable by nature and are cached internally, once a value or exception is assigned, futures cannot be modified/overwritten(hard to achieve referential transparency) .

Simple example-

import scala.concurrent.{Future}

import scala.concurrent.ExecutionContext.Implicits.global

import scala.util.{Failure, Success}



object ScalaFuture extends App {

def findingFrancis = {

println("where is your boss? you only got 1 second ,you are about to be killed by scala compiler")

Thread.sleep(1000)

println("Finding Francis...")

} // Add your logic



val whereIsFrancis = Future {

findingFrancis

// wait time until the login executes

}

}

ExecutionContext -

Think of ExecutionContext as administer who allocates new threads or use a pooled/current thread(not-recommended) to execute futures and its functions.

without importing Executioncontext into scope we cannot execute a future.

import scala.concurrent.ExecutionContext.Implicits.global

global execution context is used in many situations dodging the need to create custom execution context.

default global execution context will set the parallelism level to the number of available processors(can be increased).

Callbacks -

Futures eventually return values, which needs to be handled by callbacks.

unhandled values/exceptions will be lost.

Futures have methods that you can use. Common callback methods are:

onComplete

callback values in future are instances of Try clause -

def onComplete[U](f: Try[T] => U)(implicit executor: ExecutionContext): Unit

onSuccess and onFailure are Deprecated.use

filter

foreach

map and many more… check this

whereIsFrancis.onComplete {

case Success(foundFrancis) => println(s"heads up dummy $foundFrancis")

case Failure(exception) => println(s"F***** unreal $exception")

}



Await-

this approach is used only in system where blocking a thread is necessary, not usually not-recommended because it impacts performance .

Await.result will block the main thread and waits until the specified duration for the result.

def result[T](awaitable: Awaitable[T], atMost: Duration): T

above code will only display -

whereIsFrancis.onComplete {

case Success(foundFrancis) => println(s" found you, heads up dummy $foundFrancis")

case Failure(exception) => println(s"F***** unreal $exception")

}

Await.result(whereIsFrancis,2.seconds)

requires Duration import for seconds-

for handling of time in concurrent applications scala have

import scala.concurrent.duration._

this support different time units -

toNanos , toMicros , toMillis , toSeconds , toMinutes , toHours , toDays and toUnit(unit: TimeUnit)

For-Comprehensions-

for bulky code, callbacks are not always the best approach, Scala futures can be used with for (enumerators) yield e , where enumerators refers to a semicolon-separated list . An enumerator is either a generator which introduces new variables, or it is a filter.

val result: Future[(String, String, String)] = for {

Cable <- fromFuture

DeadPool <- notFromFuture

Colossus <- notFromFuture

} yield (Cable, DeadPool, Colossus)

Good to know-

Exceptions- When asynchronous computations throws unhandled exceptions, futures associated with those computations fail.

Failed futures store an instance of Throwable instead of the result value. Future provide the failed projection method, which allows this Throwable to be treated as the success value of another Future. Projections- To enable for-comprehensions on a result returned as an exception, futures also have projections. If the original future fails, the failed projection returns a future containing a value of type Throwable . If the original future succeeds, the failed projection fails with a NoSuchElementException

Complete Code-

import scala.concurrent.{Future}

import scala.concurrent.ExecutionContext.Implicits.global

import scala.util.{Failure, Success}

import scala.concurrent.duration._ object ScalaFuture extends App {

def findingFrancis = {

println("where is your boss? you only got 1 second ,you are about to be killed by scala compiler")

Thread.sleep(1000)

println("Finding Francis...")

} // Add your logic



val whereIsFrancis = Future {

findingFrancis

// wait time until the login executes

}

whereIsFrancis.onComplete {

case Success(foundFrancis) => println(s" found you, heads up dummy $foundFrancis")

case Failure(exception) => println(s"****** unreal $exception")

}

Await.result(whereIsFrancis, 2.seconds)

}

Output-

after making the main thread wait for 2 seconds, we get to see the output from the future thread as below,

Conclusion-

Futures are a great approach to run parallel programs in an efficient and non-blocking way.we have better ways to achieve this in more functional way using external libraries(scalaz, cats,etc).

Akka is an “actor model” library built on Scala, and provides a way to implement long-running parallel processes(to overcome limitations of Scala Futures), futures are used in other frameworks which are built using Scala like Play Framework, lagom, Apache Spark, etc.

checkout official-page for more.

Thanks for reading and the support..