CoroutineContext

481 단어·3 분·원문(.md)

In the CoroutineDispatcher section, we learned about Dispatcher and CoroutineExceptionHandler.

  • Dispatcher: A manager that holds the thread pool where coroutines will execute.
  • CoroutineExceptionHandler: A handler for when an Exception occurs in a coroutine.

Interestingly, these two elements can be placed directly where a CoroutineContext is expected.

This is possible because both Dispatcher and CoroutineExceptionHandler are implementations of interfaces that extend CoroutineContext.


CoroutineContext #

You can think of CoroutineContext as the environment in which a Coroutine executes. The Dispatcher and CoroutineExceptionHandler mentioned above are also part of the coroutine's execution environment, and both can be included in a CoroutineContext to set up the coroutine's execution environment.

Combining CoroutineContexts #

Let's combine a Dispatcher and a CoroutineExceptionHandler to create a single Context.

Here, we use the operator fun plus on CoroutineContext.

public interface CoroutineContext {
    public operator fun plus(context: CoroutineContext): CoroutineContext 
     ..
}
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->  }

val coroutineContext = Dispatchers.IO + exceptionHandler

Interpreting this code, a single CoroutineContext now includes Dispatcher.IO and CoroutineExceptionHandler, allowing the context to handle exceptions occurring on the IO Thread.

The CoroutineContext created this way can be used by placing it where a CoroutineContext is expected.


Accessing CoroutineContext #

We learned above that CoroutineContext is a collection of CoroutineContexts. Now, we will explore how to access a specific CoroutineContext from such a collection.

The image above was a simplified summary for easy understanding, but expressed in more detail, it looks like this:

There are two CoroutineContexts that make up the CoroutineContext: Dispatcher and CoroutineExceptionHandler. Let's say the key for Dispatcher is "keyA" and for CoroutineExceptionHandler is "keyB". Of course, the key is not actually a string, but we express it this way for easier understanding.

In the image above, we want to retrieve the ExceptionHandler from the parent CoroutineContext.

fun main() {
    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> }

    val coroutineContext = Dispatchers.IO + exceptionHandler // Parent CoroutineContext = Dispatcher + ExceptionHandler

    val exceptionHandlerFromContext = coroutineContext[exceptionHandler.key] // Accessing child CoroutineContext via Key

    if (exceptionHandler === exceptionHandlerFromContext) { // Comparing for identity to check if they are the same object
        println(true)
    }
}

As shown above, it's possible to request a child CoroutineContext by passing a key to the CoroutineContext.

If you compare the value retrieved from the context via the key with the previously combined CoroutineContext using identity (===) comparison, you can see that true is printed.


Removing CoroutineContext from CoroutineContext #

If CoroutineContext can be accessed as shown above, it can naturally also be removed. The removal of a CoroutineContext is possible through the minusKey() method.

fun main() {
    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> }

    val coroutineContext = Dispatchers.IO + exceptionHandler

    val minusContext = coroutineContext.minusKey(exceptionHandler.key)
}

This can be illustrated as follows:

  1. Request to remove CoroutineExceptionHandler (CoroutineContext)
  1. Removal complete
  1. Returns itself

Calling minusKey returns the removed CoroutineContext.

public fun minusKey(key: Key<*>): CoroutineContext
val minusContext = coroutineContext.minusKey(exceptionHandler.key)
Back-End/kotlin/coroutine/coroutine_context.md