1. Introduction

If you’re not familiar with CoroutineContext, it’s worth reading this article. If you don’t understand it once, read it several times. The process of writing this article also deepened my understanding of CoroutineContext. CoroutineContext is the foundation of coroutines and is worth learning

Android developers are familiar with Context. In Android, Context has a lot of powers. It can get application resources, it can get system resources, it can start activities. Context has several well-known subclasses, Activity, Application, and Service, which are very important components in your Application.

There’s a similar concept in coroutines, CoroutineContext. It is the context in which we can control the thread in which the coroutine executes, set the name of the coroutine, catch exceptions thrown by the coroutine, and so on.

We know that a coroutine can be launched using the coroutinescope.launch method. The first argument to this method is of type CoroutineContext. The default value is EmptyCoroutineContext singleton object.

Before we get started on CoroutineContext let’s look at a bit of code that you’ll often see in coroutines

Just started to learn coroutines, we often and Dispatchers. The Main, Job, CoroutineName, CoroutineExceptionHandler dealing, they are all CoroutineContext subclasses. We can easily understand them alone, Dispatchers. The Main means to coroutines distribution in the Main thread, the Job can manage the life cycle, in coroutines CoroutineName can set the name of coroutines, CoroutineExceptionHandler can catch exceptions coroutines. But the + operator will be new and confusing to most Java developers and even Kotlin developers. What does CoroutineContext+ really mean in coroutines?

The + operator simply merges two CoroutineContext into a linked list, as explained later

2. CoroutineContext Class diagram overview

According to the structure of the class diagram, we can divide it into four levels:

  1. CoroutineContext The parent interface of all context-dependent classes in a coroutine.
  2. CombinedContext, Element, EmptyCoroutineContext. They are direct subclasses of CoroutineContext.
  3. AbstractCoroutineContextElement, Job. These two are direct subclasses of Element.
  4. CoroutineName, CoroutineExceptionHandler, CoroutineDispatcher(includes dispatchers. Main and dispatchers.default). They are the direct AbstractCoroutineContextElement subclasses.

In the red box, CombinedContext defines the size() and contains() methods, much like the collection operation. CombinedContext is a collection of CoroutineContext objects, Element and EmptyCoroutineContext don’t define these methods. The only coroutine context that actually implements collection operations is the CombinedContext, which I’ll explain later

3. CoroutineContext interface

CoroutineContext: First of all,Let’s take a look at the official note. I summarize its functions as follows:

Persistent context for the coroutine. It is an indexed set of [Element] instances. An indexed set is a mix between a set and a map. Every element in this set has a unique [Key].

  1. CoroutineContext is the context of the coroutine.
  2. CoroutineContext is a set of Elements. There are no duplicate Element objects.
  3. Each element in the collection has a unique Key that can be used to retrieve elements.

Most of us will wonder why we don't just use HashSet to save an Element if it's a set. How does CoroutineContext work? The reason is that a linked list implementation is better for coroutine nesting.

Then,Let’s look at some of the methods defined by this interface

4. The Key interfaces

Key is an interface defined in CoroutineContext. As an interface, it does not declare any methods, so it has no real purpose. It is simply used for retrieval. Let’s take a look at how the Key interface is used in a coroutine library.By looking at the examples in the official coroutine library, we see that all subclasses of Element must override the Key property, and that the Key’s generic type must be the same as the class name. In the case of CoroutineName, Key is an associated object and its generic type is CoroutineName.

To make it easier to understand, I wrote the MyElement class as follows:

By comparing the KT class to the decomcompiled Java class, we can see that Key is a static variable, and its implementation class does nothing. It works like a Key in a HashMap:

  1. The key-value function is implemented to provide retrieval functions for insertion and deletion
  2. Key is a static variable that is globally unique and provides uniqueness for Element

Kotlin syntactic sugar

coroutineContext.get(CoroutineName.Key)

coroutineContext.get(CoroutineName)

coroutineContext[CoroutineName]

coroutineContext[CoroutineName.Key]

This is equivalent

5. CoroutineContext. The get method

Source code (put together, same below)

use

Interpretation of the

Retrieve Element by Key. The return value can only be Element or NULL, the value of an Element in a linked list node.

  1. Element get: Returns the Element as long as the Key matches the current Element’s Key, otherwise returns null.
  2. The CombinedContext GET method iterates through the list, looks for elements equal to the Key, and returns null if none is found.

6. CoroutineContext. Plus method

The source code

use

Interpretation of the

Combining two CoroutineContext into one CoroutineContext returns a new Element if it is two elements of the same type. If there are two different types of elements a CombinedContext is returned. If there are multiple elements of different types, a CombinedContext list is returned.

I’ve summarized the above algorithm into five scenarios, but before I introduce these five scenarios, let’s talk about the data structure of the CombinedContext.

7. CombinedContext analysis

Because CombinedContext is a subclass of CoroutineContext, and Left is of type CoroutineContext, its data structure is a linked list. We often use next to denote the next node in a linked list. So why is it called left? I even suspect that whoever wrote this code was left-handed. The real reason is that coroutines can start child coroutines, which can start child coroutines. The parent coroutine is on the left, and the child coroutine is on the right

Nested start coroutinesThe outer coroutine Context is more to the left, roughly as follows(It’s not, it’s more complicated.)

Both of the linked lists are reflected here. The coroutinecontext. plus method uses header interpolation. CombinedContext’s toString method prints the list in reverse order.

8. Five PLUS scenarios

According to the PLUS source code, I have concluded that five scenarios will be covered.

  1. plus EmptyCoroutineContext
  2. Plus Element of the same type
  3. Callers of the Plus method have no Dispatcher associated Element
  4. The callers to the Plus method only have the Dispatcher associated Element
  5. The plus method is called by a linked list of Dispatcher-related elements

The results are as follows:

  1. Dispatchers.Main + EmptyCoroutineContextResults:Dispatchers.Main.
  2. CoroutineName("c1") + CoroutineName("c2")Results:CoroutineName("c2"). The same type of direct substitution.
  3. CoroutineName("c1") + Job()Results:CoroutineName("c1") <- Job. Head insertion method by plus (Job) at the head of the list
  4. Dispatchers.Main + Job()Results:Job <- Dispatchers.Main. Although it is a header, ContinuationInterceptor must be at the head of the list.
  5. Dispatchers.Main + Job() + CoroutineName("c5")Results:Job <- CoroutineName("c5") <- Dispatchers.Main. Dispatchers.Main is at the head of the list, the others are headers.

If you leave out the dispatchers. Main case. We can replace + with <-. CoroutineName(” C1 “) + Job() equals CoroutineName(“c1”) <- Job

9. CoroutineContext minusKey method

The source code

Interpretation of the

  1. Element minusKey method: Return EmptyCoroutineContext if the Key is equal to the current Element Key, otherwise return the current Element without subtracting
  2. CombinedContext minusKey method: delete the eligible nodes in the linked list in three cases.

Take the following linked list as an example for the three cases

Job <- CoroutineName(“c5”) <-Dispatchers.Main

  1. Node not found: minusKey(MyElement). NewLeft === left branch at Job node, and so on, same branch at CoroutineName, same branch at dispatchers. Main.

  2. The node is at the end: minusKey(Job). At CoroutineName(“c5”), follow the newLeft === EmptyCoroutineContext branch and recurse to the header

  3. The node is not at the end: minusKey(CoroutineName). Go to the else branch at dispatchers. Main

10. Summary

To learn CoroutineContext, first of all, we need to make clear the inheritance relationship between various types. Secondly, CombinedContext is a collection of specific elements, whose data structure is linked list. If readers are familiar with the operation of adding, deleting, changing and checking linked list, It’s easy to figure out the CoroutineContext principle, otherwise it’s like trying to figure out a CoroutineContext.

Remember to follow the official wechat account of Bytecode