Think - Parallel , multi threaded, distributed, Asynchronous, non-blocking programming.first thing that comes to mind for short-lived concurrent processes = “Scala Futures” .
Future’s allow us to run values off main thread ,and handle values which are running in the background /yet to be executed by mapping them with callbacks .
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 ofThrowable
instead of the result value.Future
provide thefailed
projection method, which allows thisThrowable
to be treated as the success value of anotherFuture.
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 typeThrowable
. If the original future succeeds, thefailed
projection fails with aNoSuchElementException
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..