0% found this document useful (0 votes)
71 views8 pages

43 Future - Get Programming With Scala

This document discusses asynchronous computations using Futures in Scala. It explains what Futures are, how to create instances of Future, and how to process the result of a Future once it completes. Some key points are that Futures allow defining asynchronous operations, the ExecutionContext parameter is required to define where the asynchronous computation runs, and the result of a Future can be processed after it completes.

Uploaded by

kr.manid
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
71 views8 pages

43 Future - Get Programming With Scala

This document discusses asynchronous computations using Futures in Scala. It explains what Futures are, how to create instances of Future, and how to process the result of a Future once it completes. Some key points are that Futures allow defining asynchronous operations, the ExecutionContext parameter is required to define where the asynchronous computation runs, and the result of a Future can be processed after it completes.

Uploaded by

kr.manid
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 8

43 Future

After reading this lesson, you will be able to

Represent an asynchronous computation using Future


Process its result on completion

In the previous lesson, have learned about implicit parameters and val-
ues; you’ll now see them in action when defining an instance of type
Future . You’ll discover the difference between synchronous and asyn-
chronous computations and how they can affect your program’s perfor-
mance. You’ll see how you can use the type Future to define asyn-
chronous computations that can fail. An operation is asynchronous if
your program can continue to run without waiting for its outcome. You’ll
also process its result once completed. In the capstone, you’ll use the type
Future to read and write data to a database asynchronously for your
quiz application.

Consider this

Imagine you are developing an application to book tickets for events. It


needs to accept and process as many booking requests as possible at the
same time. How would you structure your application so that it can max-
imize its available resources?

43.1 Why Future?

Latency is the time spent between requesting an operation and getting a


response back. Brendan Gregg discusses how system operations can dif-
fer in duration in his book Systems Performance: Enterprise and the Cloud
(Prentice Hall, 2014). For example, a 3.3 GHz processor can execute on av-
erage one CPU cycle in 0.3 nanoseconds (ns) and a rotational hard disk I/O
operation in up to 10 milliseconds (ms). As humans, we struggle to grasp
how different these numbers are, so Gregg decided to scale these and oth-
er system events to a more understandable scale; you can see them in ta-
ble 43.1. If you scale one CPU cycle up to 1 second, a single I/O operation
can last up to 12 months!
Table 43.1 Example time scale of system latencies, taken from Brendan
Gregg’s Systems Performance: Enterprise and the Cloud (Prentice Hall,
2014, p. 20)

Event Latency Scaled

One CPU cycle 0.3 ns 1s

Level 1 cache access 0.9 ns 3s

Level 2 cache access 2.8 ns 9s

Level 3 cache access 12.9 ns 43 s

Main memory access (DRAM, from CPU) 120 ns 6 min

Solid-state disk I/O (flash memory) 50–150 2-6 days


µs

Rotational disk I/O 1–10 ms 1–12 months

Internet: San Francisco to New York 40 ms 4 years

Internet: San Francisco to United 81 ms 8 years


Kingdom

Internet: San Francisco to Australia 183 ms 19 years

TCP packet retransmit 1–3 s 105–317


years

OS virtualization system reboot 4s 423 years

SCSI command time-out 30 s 3 millennia

Hardware (HW) virtualization system 40 s 4 millennia


reboot

Physical system reboot 5m 32 millennia

Programs traditionally perform operations synchronously: they wait for


an instruction to finish before starting with the next one. This approach is
a popular strategy, but it has a considerable performance cost when deal-
ing with procedures with significant latency. Another option is to perform
operations asynchronously: starting the next instruction without waiting
for the current one to be complete. This approach can make your pro-
gram do more with its resources as it will not spend as much time waiting
for operations to complete, but its execution flow will be more difficult to
predict. Your CPU can split a program into smaller independent se-
quences of instructions called threads of executions, or just threads. A
multithreaded platform, such as the JVM, allows your program to run
more than one thread simultaneously, making asynchronous computa-
tions possible.
The type Future allows you to define an operation to execute asynchro-
nously: the JVM delegates its execution to another thread, it moves on to
other instructions, and it returns to it when notified of its completion.

43.2 Creating an instance of Future

Imagine you are developing a program to handle orders for a store. You
need to confirm a product’s availability with its warehouse via an HTTP
API, a known bottleneck of your application. You can perform it asyn-
chronously using Future to improve its overall performance.

Listing 43.1 Checking product availability

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

def isProductAvailable(productId: Int,


quantity: Double): Future[Boolean] = Future {
requestAvailability(productId, quantity)
}

private def requestAvailability(productId: Int,


quantity: Double): Boolean = ???

Importing the class Future

Adding the implicit global execution context to your scope. It contains


information about the resources available to your program.

Calling the function Future.apply

In Scala, the class Future allows you to specify an operation to execute


asynchronously: a separate thread eventually completes its execution
while your program’s current thread processes other instructions. The
method Future.apply allows you to create an instance of Future .

Listing 43.2 The method Future.apply

package scala.concurrent

object Future {

def apply[T](r: => T)


(using ec: ExecutionContext): Future[T] = {
???
}
}

The parameter is not evaluated until needed.

The ExecutionContext defines the available set of threads to use.

Implementation omitted as nontrivial

First, include the companion object Future in your scope by adding im‐
port scala.concurrent.Future . Then, invoke its method
Future.apply by providing the instructions to execute asynchronously
and with an execution context. Figure 43.1 compares the signature of
Try.apply with Future.apply . The two methods look very similar.
However, Future.apply requires an extra implicit parameter of type
ExecutionContext , which provides information that makes the asyn-
chronous computation possible.

Figure 43.1 A comparison between the apply method for Try and
Future . The first represents synchronous computations that can fail, the
second asynchronous ones. They both delay the execution of an expres-
sion. However, the Future one requires an instance of
ExecutionContext , which contains information about the resources
and execution strategy.

The ExecutionContext class gives your Future instance a thread pool


to use and an execution strategy to follow. Scala offers a default imple-
mentation for it called ExecutionContext.global , which is designed
to work in the majority of the cases. You can define a custom execution
context, but it goes beyond this book’s scope. This is nontrivial and can
cause significant performance degradation to your application if not done
carefully. You can include ExecutionContext.global as implicit in
your scope by adding the following instruction:

import scala.concurrent.ExecutionContext.Implicits.global

This expression is equivalent to defining the following implicit value:

import scala.concurrent.ExecutionContext
using val ec = ExecutionContext.global

ExecutionContext as an implicit parameter rather than an import

When developing a large application, you should prefer passing your


ExecutionContext as an implicit parameter to your classes and func-
tions rather than directly using the global execution context. You could
then select which execution context to use in one place of your codebase,
usually its entry point. This approach gives you more control over which
execution context your program uses and is easier to customize if
needed.

Let’s see a few more examples of how you can use the method
Future.apply :

scala> import scala.concurrent.Future


import scala.concurrent.Future

scala> Future(10/2)
error: Cannot find an implicit ExecutionContext. You might pass
an (using ec: ExecutionContext) parameter to your method
or import scala.concurrent.ExecutionContext.Implicits.global.
Future(10/2)
^
// You need to provide an ExecutionContext to Future.apply!

scala> import scala.concurrent.ExecutionContext.Implicits.global


import scala.concurrent.ExecutionContext.Implicits.global

scala> Future(10/2)
val res0: scala.concurrent.Future[Int] = Future(Success(5))
// Its execution has completed with success
// before the REPL displays its content

scala> val tenOverZero = Future(10/0)


val tenOverZero: scala.concurrent.Future[Int] = Future(<not completed>)
// This time, its execution has not completed yet

scala> tenOverZero
val tenOverZero: scala.concurrent.Future[Int] = Future(Failure(java.lang.ArithmeticExcept
// Checking its content again after some time,
// it has completed with a failure

scala> val foo = {


| println("Hello")
| Future(println("World"))
| println("!")
| }
Hello
!
World
val foo: Unit = ()
// Your program does not wait for the Future instance to complete
// and it moves on to print the text "!"

QUICK CHECK 43.1 Consider the following function. Does it com-


pile? What output does it produce when invoked?

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

def test(): Unit = {


Future(('a' to 'c').map(print))
(0 to 2).map(print)
}

43.3 Processing Future on completion

Consider your program to place orders in a store. Suppose that the third-
party API can provide information on the quantity available for a certain
product. You want to keep track of it to help the store decide on the items
to restock.

Listing 43.3 Tracking the availability of a product

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

case class Availability(id: Int, quantity: Double)


def trackAvailability(availability: Future[Availability]): Unit =
availability.onComplete {
case Success(p) if p.quantity <= 0 =>
println(s"Product ${p.id} is not available")
case Success(p) =>
println(s"Product ${p.id} has available quantity ${p.quantity}")
case Failure(ex) =>
println(s"Couldn't get the availability " +
s"because of ${ex.getMessage}")
}

It processes the result of a completed future as an instance of Try.

You can use pattern matching on many of the types you have encoun-
tered; unfortunately, Future is not one of them. A pattern-matching op-
eration is synchronous as it requires the value wrapped inside your type
to be available. For an instance of Future[T] , the method onComplete
allows you to execute a function once completed:

def onComplete[U](f: Try[T] => U)


(using executor: ExecutionContext): Unit

The function onComplete returns a value of type Unit . Any result that
your callback function f produces will be lost. If you need to retain its
result, you should consider using its method map instead. You’ll learn
more about this in the next lesson. A few more examples of how to use
the method onComplete are the following:

scala> Future(10/2).onComplete { value =>


| if (value.isFailure) println("bad!")
| else println("good!")
| }
good!
// It prints "good!" to the console

scala> Future(10/2).onComplete { value =>


| if (value.isFailure) "bad!"
| else "good!"
| }
// It produces no value because it discards the string value

QUICK CHECK 43.2 Consider the following function. Does it com-


pile? What output does it produce when invoked?

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

def isSuccess[T](f: Future[T]): Unit = f.onComplete(_.isSuccess)

Summary

In this lesson, my objective was to introduce you to asynchronous compu-


tation using the type Future .

You learned that an asynchronous computation allows your program


to execute other instructions without waiting for it to complete first.
You saw how to create an instance of type Future using the global
execution context.
You discovered how to use the method onComplete to process a val-
ue provided by an asynchronous computation.

Let’s see if you got this!

TRY THIS The following snippet of code prints all the files in the cur-
rent directory to the terminal. Change its code to execute it
asynchronously.

import java.io.File
new File(".").listFiles().foreach(println)

Answers to quick checks

QUICK CHECK 43.1 The function test compiles: an instance of


ExecutionContext .global is available through the import of
scala.concurrent.ExecutionContext .Implicits.global . Its
output is non-deterministic: it changes based on when how the executor
schedules its threads.

scala> import scala.concurrent.Future


| import scala.concurrent.ExecutionContext.Implicits.global
|
| def test(): Unit = {
| Future(('a' to 'c').map(print))
| (0 to 2).map(print)
| }
|
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
def test: ()Unit

scala> test()
abc012
scala> test()
0abc12
scala> test()
012abc

QUICK CHECK 43.2 The function isSuccess compiles, but it re-


turns no value. When your Future instance completes, the callback
produces a Boolean value that the method onComplete then discards.

scala> import scala.concurrent.ExecutionContext.Implicits.global


| import scala.concurrent.Future
|
| def isSuccess[T](f: Future[T]): Unit = f.onComplete(_.isSuccess)
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
def isSuccess: [T](f: scala.concurrent.Future[T])Unit

scala> isSuccess(Future("hello"))

scala> isSuccess(Future(throw new Exception("BOOM!")))

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy