🎓 A1Learner
🔍
0 of 48 lessons completed

Scala Execution Context

An ExecutionContext is the worker pool that runs Future jobs. It decides how many jobs can run at the same time.

The global pool runs a Future

The global pool is the shared default. Import it, then a Future runs on it.

import scala.concurrent.{Future, Await}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt

@main def main(): Unit =
  val f = Future { 4 * 5 }
  val result = Await.result(f, 5.seconds)
  println(result)

A custom pool can run one job at a time

A single-thread executor makes a pool with one worker. The Future runs on that worker, and the executor is shut down after the wait.

import scala.concurrent.{Future, Await, ExecutionContext}
import scala.concurrent.duration.DurationInt
import java.util.concurrent.Executors

@main def main(): Unit =
  val executor = Executors.newSingleThreadExecutor()
  given ExecutionContext = ExecutionContext.fromExecutor(executor)
  val f = Future { 6 * 7 }
  val result = Await.result(f, 5.seconds)
  executor.shutdown()
  println(result)

One custom pool can run two jobs

Both Futures use the same custom pool. Only their awaited sum is printed, so the order the jobs run in does not matter.

import scala.concurrent.{Future, Await, ExecutionContext}
import scala.concurrent.duration.DurationInt
import java.util.concurrent.Executors

@main def main(): Unit =
  val executor = Executors.newSingleThreadExecutor()
  given ExecutionContext = ExecutionContext.fromExecutor(executor)
  val first = Future { 5 }
  val second = Future { 10 }
  val total = Await.result(first, 5.seconds) + Await.result(second, 5.seconds)
  executor.shutdown()
  println(total)

Example

import scala.concurrent.{Future, Await, ExecutionContext}
import scala.concurrent.duration.DurationInt
import java.util.concurrent.Executors

@main def main(): Unit =
  val executor = Executors.newSingleThreadExecutor()
  given ExecutionContext = ExecutionContext.fromExecutor(executor)
  val first = Future { 12 }
  val second = Future { 8 }
  val total = Await.result(first, 5.seconds) + Await.result(second, 5.seconds)
  executor.shutdown()
  println(total)

Key points

  • ExecutionContext: the worker pool that runs the work inside a Future.
  • Global: ExecutionContext.Implicits.global is the shared default pool.
  • Import: importing global brings that shared pool into scope.
  • Custom: a program can make its own pool from a single-thread executor.
  • Shutdown: shut a custom executor down after the work finishes, so the program can end.

In this example, a single-thread executor is turned into a custom pool and given as the context for the Futures. The two Futures hold 12 and 8, their awaited values are added, the executor is shut down, and the total 20 is printed.

Note

the global pool is shared. A custom pool keeps a program's jobs apart from other jobs.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Scastie

Exercise

Why should a custom executor be shut down after the work finishes?