This is a good series of articles on coroutine cancellations and exceptions. I have been preparing for translation, but for various reasons, it has been delayed until now.
Originally written by manuelvicnt
Coroutines: First things First
Translator: Bingxin said
This series of blogs explores coroutine cancellations and exceptions in depth. Cancelling saves memory and battery life by avoiding unexpected work; Proper exception handling can lead to a good user experience. As the basis for the other two articles in this series, it is important to understand some of the basic concepts of coroutines, such as CoroutineScope, Job, CoroutineContext, and so on.
If you prefer video, check out Florina Muntenescu’s talk with me at KotlinConf’19.
CoroutineScope CoroutineScope
CoroutineScope helps you track any coroutines launched via launch and Async. They are all extension functions of the CoroutineScope. A running coroutine can be stopped at any point in time by calling scope.cancel().
CoroutineScope should be created whenever you start a coroutine on any page of your App and control its lifecycle. In Android, the KTX class library already provides CoroutineScope for specific lifecycle classes, such as viewModelScope and lifecycleScope.
The CoroutineContext(CoroutineContext) argument is provided to the constructor when the CoroutineScope is created. The following code demonstrates how to create a new scope and coroutine.
// Job and Dispatcher are combined as CoroutineContext, which will be explained later
val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
/ / new coroutines
}
Copy the code
Job
Job represents a coroutine. For each coroutine launched via launch and Async, a Job instance is returned to uniquely identify and manage the life cycle of the coroutine. As shown in the previous section, you can pass a Job to the CoroutineScope to control its life cycle.
CoroutineContext(CoroutineContext)
Can be translated into coroutine context. But I’ll stick to English.
CoroutineContext is a series of elements that define the behavior of coroutines. It consists of the following parts:
- Job, manages the life cycle of coroutines
- CoroutineDispatcher, which dispatches tasks to appropriate threads
- CoroutineName, the name of the coroutine, used for debugging
- CoroutineExceptionHandler, an uncaught exception handling, this is the third article content
What is the CoroutineContext of a new coroutine? We already know that a new Job will be created to help us manage the life cycle, and the remaining elements will inherit from its parent’s CoroutineContext (possibly another coroutine, or the CoroutineScope that created it).
Since CoroutineScope can create coroutines, and you can create multiple coroutines within a single coroutine. This creates an implicit hierarchy. In addition to creating new coroutines using CoroutineScope, the code below shows how to create multiple coroutines within a single coroutine.
val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
// The parent of the new coroutine is scope
val result = async {
// The parent of the new coroutine here is the scope. Launch coroutine above
}.await()
}
Copy the code
The root of the hierarchy is usually the CoroutineScope. We can think of the hierarchy as follows:
Job Life Cycle
Jobs go through the following life cycle:
New, Active, Completing, Completed, Cancelling , Cancelled
The status of the Job can be obtained using the attributes isActive, isCancelled, and isCompleted.
When the coroutine isActive, failure or cancellation moves the coroutine to the Cancelling state (isActive = false, isCancelled = true). When all children of the coroutine have completed their tasks, the coroutine will enter the Cancelled state (isCompleted = true).
About the Parent CoroutineContext
In the inheritance structure of coroutines, each coroutine has a parent, which may be a CoroutineScope or another coroutine. But the child coroutine’s final parent CoroutineContext may not be the same as its parent’s original CoroutineContext.
The parent CoroutineContext can be calculated as follows:
Parent Context = Defaults + CoroutineContext + arguments
Among them:
- Some of these elements have default values:
CoroutineDispatcher
The default value forDispatchers.Default
,CoroutineName
The default value forcoroutine
- inherited
CoroutineContext
It’s dad’s CoroutineContext - Parameters passed to the coroutine builder take precedence over those inherited from the context
Note: Multiple CoroutineContext can be joined by the + operation. Since CoroutineContext contains a list of elements, when a new CoroutineContext is created, the elements to the right of the + will override the elements to the left. For example :(dispatchers. Main, “name”) + (dispatchers. IO) = (dispatchers. IO, “name”).
The coroutine created through this coroutine scope will contain at least the elements shown above. CoroutineName is grayed out because it is the default.
Now we know what the parent CoroutineContext of a new coroutine is. Its own CoroutineContext actually looks like this:
New coroutine context = parent CoroutineContext + Job()
Normally the coroutine scope above creates a new coroutine:
val job = scope.launch(Dispatchers.IO) {
/ / new coroutines
}
Copy the code
So what does its parent CoroutineContext and its own CoroutineContext look like? Look at the pictures below.
Note that the next Job and the next Job are not the same instance; a new coroutine always gets a new Job instance.
The coroutine scheduler for the ultimate parent CoroutineContext is dispatchers.io because it is overridden by parameters in the coroutine builder. Scope.launch (dispatchers.io).
Also, notice that the Job instance in the parent CoroutineContext is the Job instance of scope (red), and the Job in the CoroutineContext passed to the new coroutine is a new instance (green).
In this third article in the series, we’ll see that the CoroutineScope is capable of handling another Job implementation class, the Container Job, that changes the scope of the coroutine for exception handling. Therefore, subroutines created in such a CoroutineScope will inherit the Job of type SupervisorJob. However, if the parent coroutine is another coroutine, it will always be of type Job.
Now that you know the basics of coroutines, learn more about cancellations and exceptions in the next two articles in this series.
This is the end of today’s article, there are three articles in this series, all very wonderful, scan the qr code below continue to pay attention to!
This article is formatted using MDNICE