Execution context is the place where a future is scheduled and executed. Execution context contains a threads pool from which threads are used to execute given tasks.
Below example shows creating an execution context using a single thread execution service.
val executor: ExecutorService = Executors.newFixedThreadPool(1)
val ec: ExecutionContext = ExecutionContext.fromExecutor(executor)
If we want to execute a future, we need to provide an implicit execution context to Future.apply
so that it schedules the future in the execution service. For example,
def heavyTask(sr: Int): Future[Unit] = Future {
printWithThread(s"Task ${sr} has been started");
Thread.sleep(5000)
}
heavyTask
calls Future
to execute the task off main thread. Without an implicit execution context, it leads to compilation error.
Creating execution context
An execution context can be created using Executor
and ExecutorService
interfaces. These interfaces are only responsible for scheduling and executing the tasks.
Using Executor Interface
We can use Executors
factory to create Executor
implementations.
val executor = Executors.newSingleThreadExecutor()
given ec: ExecutionContext = ExecutionContext.fromExecutor(executor)
Now ec
can be used to execute Futures.
Using ExecutorService
val executorService: ExecutorService = Executors.newFixedThreadPool(2)
given ec: ExecutionContext = ExecutionContext.fromExecutor(exectorService)
ec
has executorService with a thread pool of two threads. This can schedule tasks concurrently on two threads.
Tips of using execution context
- Try to use different execution contexts for different part of the application. Avoid using same context everywhere. There might be different requirements of concurrency at different places.
- Using sensible names for thread pools