Coroutines provide a flexible and easy way to do asynchronous programming. Unlike threads, coroutines support structured concurrency, which means they provide more control over handling multiple coroutines. In simple words, this makes the process of creating, cancelling, and handling errors of multiple coroutines easier. Coroutines achieve this by using coroutine scopes. New coroutines can only be launched inside a scope, which determines the lifecycle of the coroutine.
Understanding coroutine scopes is essential if you want to work with coroutines. Let me break it down for you in simple terms.
If you examine the implementation, a coroutine scope is an interface that contains only one property - coroutine context. The coroutine context stores additional technical information used to run a given coroutine, like the coroutine custom name, or the dispatcher specifying the threads the coroutine should be scheduled on.
Coroutine Scope and Structured Concurrency
Now, let’s understand the role of coroutine scope in structured concurrency. The outer/parent scope stays alive until all the coroutines are completed. All the child coroutines can be cancelled by simply cancelling the parent scope, ensuring that the coroutines don’t get leaked.
We manage the lifecycles of our coroutines by creating an instance of CoroutineScope. The recommended way of creating coroutine scope is by using factory methods CoroutineScope() or MainScope(). The former creates a general-purpose scope, while the latter creates a scope for UI applications and uses Dispatchers.Main as the default dispatcher.
Here is a sample usage of CoroutineScope():
We can also create a custom scope by simply extending from the CoroutineScope interface. However, this is not a recommended approach. This is what a simple custom coroutine scope looks like.
If you see the above snippets, creating a job object is important. The job is a cancellable element with a life cycle. It holds the state of coroutines and is used as a handle to manage coroutines. The coroutines can be cancelled by invoking coroutineContext.cancel() which internally calls the job.cancel().
The CoroutineScope() method creates a Job() if you don’t pass one. The MainScope() uses a special type of Job called SupervisorJob.
We’ll discuss the coroutine context and job in upcoming parts. In the next post, we’ll discuss creating coroutines using coroutine builders.