Kotlin’s syntax, which is very different from Java’s, has always bothered me, and this has become obvious while learning coroutines. The coroutinecontext.plus () method, for example, has never been understood.

First the conclusion:

  1. Each CoroutineContext class has a unique object that has static Final Element = its own.class
  2. As with Map, each CoroutineContext can have only one Element of the same type. If contextA + contextB has the same Element in B, remove the same Element from A and place it in B’s Element.
  3. ContinuationInterceptor must be placed at the end of the Element list in CoroutineContext, primarily dipatchers.xx for thread switching operations. This is not important when analyzing the Plus () method.

So now I’m going to go through it line by line.

Class val scope0 = EmptyCoroutineContext Scope0 = EmptyCoroutineContext kotlin.coroutines.EmptyCoroutineContext val scope1 = CoroutineName("Hello") CoroutineName(Hello) : kotlinx.coroutines.CoroutineName val scope2 = scope1 + Dispatchers.Main scope2 = [CoroutineName(Hello), Main] : kotlin.coroutines.CombinedContext val scope3 = scope2 + Dispatchers.IO scope3 = [CoroutineName(Hello), Dispatchers.IO] : kotlin.coroutines.CombinedContext val scope4 = scope3 + EmptyCoroutineContext scope4 = [CoroutineName(Hello), Dispatchers.IO] : kotlin.coroutines.CombinedContext val scope5 = scope4 + CoroutineName("World") scope5 = [CoroutineName(World), Dispatchers.IO] : kotlin.coroutines.CombinedContextCopy the code

What is the problem with the plus() method?

public operator fun plus(context: CoroutineContext): CoroutineContext =
    if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
        context.fold(this) { acc, element ->
            val removed = acc.minusKey(element.key)
            if (removed === EmptyCoroutineContext) element else {
                // make sure interceptor is always last in the context (and thus is fast to get when present)
                val interceptor = removed[ContinuationInterceptor]
                if (interceptor == null) CombinedContext(removed, element) else {
                    val left = removed.minusKey(ContinuationInterceptor)
                    if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                        CombinedContext(CombinedContext(left, element), interceptor)
                }
            }
        }
Copy the code

The main question is: what is fold(this) {acc, element}?

What is a fold ()?

All CoroutineContext is a subclass of the three classes that implement the fold() method

object EmptyCoroutineContext public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = initial interface Element CoroutineName implements Element Dispatchers.XX implements Element public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = operation(initial, this) class CombinedContext public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = operation(left.fold(initial, operation), element)Copy the code

With these three folds () implementations, you can analyze what plus() does line by line.

In the following analysis, the left side of + is represented by A and the right side by B -> A + B

scope0

It is just a plain EmptyCoroutineContext that serves as the initialization value.

scope1 = EmptyCoroutineContext + CoroutineName(“Hello”)

  1. First check if B is Empty. No

  2. B.fold(A) {} enter CoroutineName-> element.fold (A) {}

  3. The second argument to fold() is A two-parameter function, which executes: element.fold (A) {acc=A, Element =B}

  4. Plus (): subtracts B from A using the respective Element as the Key. A is Empty. Empty.minuskey (Key) returns this regardless of subtracting anything.

  5. If one side is already Empty and you add anything to it, you end up with the other side, you just return B.

  6. Final result =B=CoroutineName(“Hello”)

scope2 = CoroutineName(“Hello”) + Dispatchers.Main

  1. First check if B is Empty. No

  2. B.old (A) {} enter dispatchers.main -> element.fold (A) {}

  3. Element. Fold (A) {acc=A, Element =B}

  4. Val removed = to remove B from A: Element. MinusKey (key) Checks whether the key is equal to its own key. If the key is equal, it subtracts itself. If the key is unequal, it cannot be subtracted.

  5. Removed =CoroutineName(“Hello”), Element = dispatchers.main

scope3 = [CoroutineName(“Hello”), Dispatchers.Main] + Dispatchers.IO

  1. First check if B is Empty. No

  2. IO-> element.fold (A) {}

  3. Element.fold(A) { acc=A, element=B }

  4. Val removed = A – B, A = CombinedContext = combined.element.key = b.kay, left=CoroutineName(“Hello”)

  5. Removed =CoroutineName(“Hello”), Element = dispatchers.io)

scope4 = [CoroutineName(“Hello”), Dispatchers.IO] + EmptyCoroutineContext

  1. [CoroutineName(“Hello”), dispatchers.io]

scope5 = [CoroutineName(“Hello”), Dispatchers.IO] + CoroutineName(“World”)

  1. First check if B is Empty. No

    1. B.fold(A) {} enter CoroutineName-> element.fold (A) {}
  2. Element.fold(A) { acc=A, element=B }

  3. Val removed = A-B, where A is A CombinedContext type

    (1) determine whether combined.element.key = b.key

    (2) subtract b.key from combined. Left = CoroutineName = combined

    (3) CoroutineName->Element subtracts to Empty, return Element = dispatchers. IO

  4. Removed is not Empty. Go on

  5. Removed Determines if there is a removed interceptor. This time it is, and records it using Interceptor = dispatchers. IO

  6. Removed from the interceptor, left=Empty, returns CombinedContext(Element =CoroutineName(“World”), Interceptor = dispatchers. IO) implementation interceptors must come last

  7. Removed =[SupervisorJob, dispatchers.io] let’s look at the final branch of Plus (), starting at point 6

    (1) Check whether removed has an interceptor. If yes, use interceptor= dispatchers. IO to record it

    (2) Removing the interceptor from the removed removing removes the left=SupervisorJob

    (3) left ! CombinedContext(left=SupervisorJob, element=B=CoroutineName(“World”)) interceptor=Dispatchers.IO)

    (4) The conclusion is the same: if there are the same keys, replace them, and Dispatchers are put at the end