background
Started guide
The code field

Cancellation is especially important when we need to avoid redundant processing to reduce memory waste and save power; Proper exception handling is also key to improving the user experience. This article is the foundation for the other two articles (the second and third will explain coroutine cancellation and exception handling, respectively), so it’s worth explaining some of the core concepts of coroutines first. Such as CoroutineScope (CoroutineScope), Job (task), and CoroutineContext (CoroutineContext) so that we can learn more deeply.

CoroutineScope

CoroutineScope keeps track of every coroutine you create through launch or Async (which are CoroutineScope extension functions). Work in progress (running coroutines) can be cancelled at any time by calling scope.cancel().

You need to create a CoroutineScope when you want to start or control the life cycle of a coroutine at one level of your application. For some platforms, such as Android, there are already libraries like KTX that provide CoroutineScope in the lifecycle of some classes, such as viewModelScope and lifecycleScope.

When the CoroutineScope is created, it takes CoroutineContext as an argument to the constructor. You can create a new scope and coroutine with the following code:

Val scope = CoroutineScope(Job() + dispatchers.main) val Job = Scope.launch {// new coroutine}Copy the code

Job

Job is used to process coroutines. For each coroutine you create (via launch or Async), it returns a Job instance that uniquely identifies the coroutine and is responsible for managing its life cycle. As we saw above, you can pass the Job instance to the CoroutineScope to control its life cycle.

CoroutineContext

CoroutineContext is a set of elements used to define the behavior of coroutines. It consists of the following items:

  • Job: Controls the life cycle of coroutines.
  • CoroutineDispatcher: Dispatches tasks to appropriate threads
  • CoroutineName: The name of a coroutine, useful for debugging;
  • CoroutineExceptionHandler: * hasn’t been caught exception handling, in the third article in the future will have a detailed explanation.

So for a newly created coroutine, what is its CoroutineContext? We already know that an instance of the Job will be created to help us control the life cycle of the coroutine. The remaining elements are inherited from the CoroutineContext’s parent, which may be another coroutine or the CoroutineScope that created it.

Because CoroutineScope can create coroutines, and you can create more coroutines within them, there is an implicit task hierarchy inside. In the code snippet below, in addition to creating new coroutines via CoroutineScope, see how to create more coroutines in coroutines:

Val scope = CoroutineScope(Job() + dispatchers.main) val Job = scope.launch {// The new coroutine will use the CoroutineScope as the parent val result = Async {// New coroutines created via launch will have the current coroutine as parent}.await()}Copy the code

The root of the hierarchy is usually the CoroutineScope. The graphical hierarchy is shown below:

△ Coroutines are executed in task level order. The parent is a CoroutineScope or some other coroutine

Job life cycle

A task can contain a series of states: newly created, Active, Completing, Completed, Cancelling and Cancelled. Although we cannot access these states directly, we can access the attributes of the Job: isActive, isCancelled, and isCompleted.

▽ Life cycle of Job

Parse the parent CoroutineContext

At the task level, each coroutine has a parent object, either a CoroutineScope or another coroutine. However, the parent coroutine CoroutineContext is not the same as the parent coroutine CoroutineContext because of the following formula:

Parent context = default value + inherited CoroutineContext + parameter

Among them:

  • Some elements contain Default values: dispatchers. Default is the Default for CoroutineDispatcher, and “coroutine” is the Default for CoroutineName;
  • The inherited CoroutineContext is the CoroutineScope or CoroutineContext of its parent coroutine;
  • Parameters passed to the coroutine Builder take precedence over inherited context parameters, so the corresponding parameter values are overridden.

Note that CoroutineContext can be merged using the “+” operator. Since a CoroutineContext is made up of a set of elements, the element to the right of the plus sign overwrites the element to the left of the plus sign to form the newly created CoroutineContext. For example, (dispatchers. Main, “name”) + (dispatchers. IO) = (dispatchers. IO, “name”).

△ Every coroutine created by the CoroutineScope, CoroutineContext will contain at least these elements. Here the CoroutineName is grayed out because the value is derived from the default parameter value.

So now we know what the new coroutine’s parent CoroutineContext looks like. Its actual CoroutineContext is:

New CoroutineContext = parent CoroutineContext + Job()

If we use the CoroutineScope shown above, we can create a new coroutine like this:

Val job = scope.launch(dispatchers.io) {// new coroutine}Copy the code

What does the coroutine parent CoroutineContext and its actual CoroutineContext look like? Take a look at the picture below.

The Job in CoroutineContext and the parent context cannot pass through an instance, because a new coroutine always gets a new instance of the Job

In part 3 of our series, the CoroutineScope will have another Job implementation called the SupervisorJob that is contained in its CoroutineContext, which changes the way the CoroutineScope handles exceptions. Therefore, new coroutines created by this Scope object will be handling a container Job as its parent. However, when the parent of a coroutine is another coroutine, the parent Job is still of type Job.

Now that you understand some of the basic concepts of coroutines, we will continue to explore coroutine cancellation in depth in part 2 and exception handling in part 3. Please stay tuned for updates if you are interested.

Click here toCheck out Android’s official Chinese documentation – Write better Android apps faster with Kotlin