协程嵌套协程
重点 (Top highlight)
This series of blog posts goes in-depth into cancellation and exceptions in Coroutines. Cancellation is important for avoiding doing more work than needed which can waste memory and battery life; proper exception handling is key to a great user experience. As the foundation for the other 2 parts of the series (part 2: cancellation, part 3: exceptions), it’s important to define some core coroutine concepts such as CoroutineScope
, Job
and CoroutineContext
so that we all are on the same page.
该系列博客文章深入探讨了协程中的取消和异常。 取消对于避免做过多的工作很重要,因为这可能会浪费内存和电池寿命。 正确的异常处理是获得良好用户体验的关键。 作为本系列其他两个部分( 第2部分:取消 , 第3部分:异常 )的基础,定义一些核心的协程概念(如CoroutineScope
, Job
和CoroutineContext
非常重要,这样我们才能在同一页面上。
If you prefer video, check out this talk from KotlinConf’19 by Florina Muntenescu and I:
如果您喜欢视频,请查看Florina Muntenescu和我在KotlinConf'19上的演讲 :
协同范围 (CoroutineScope)
A CoroutineScope
keeps track of any coroutine you create using launch
or async
(these are extension functions on CoroutineScope
). The ongoing work (running coroutines) can be canceled by calling scope.cancel()
at any point in time.
CoroutineScope
会跟踪您使用launch
或async
创建的任何协程(这些是CoroutineScope
上的扩展功能)。 可以通过在任何时间点调用scope.cancel()
来取消正在进行的工作(正在运行的协程)。
You should create a CoroutineScope
whenever you want to start and control the lifecycle of coroutines in a particular layer of your app. In some platforms like Android, there are KTX libraries that already provide a CoroutineScope
in certain lifecycle classes such as viewModelScope
and lifecycleScope
.
每当您要启动和控制应用程序特定层中协程的生命周期时,都应创建一个CoroutineScope
。 在某些平台(如Android)中,已经有KTX库在某些生命周期类(例如viewModelScope
和lifecycleScope
提供了CoroutineScope
。
When creating a CoroutineScope
it takes a CoroutineContext
as a parameter to its constructor. You can create a new scope & coroutine with the following code:
创建CoroutineScope
,它将CoroutineContext
作为其构造函数的参数。 您可以使用以下代码创建新的合并范围和协程:
// Job and Dispatcher are combined into a CoroutineContext which
// will be discussed shortly
val scope = CoroutineScope(Job() + Dispatchers.Main)val job = scope.launch {
// new coroutine
}
工作 (Job)
A Job
is a handle to a coroutine. For every coroutine that you create (by launch
or async
), it returns a Job
instance that uniquely identifies the coroutine and manages its lifecycle. As we saw above, you can also pass a Job
to a CoroutineScope
to keep a handle on its lifecycle.
Job
是协程的句柄。 对于您创建的每个协程(通过launch
或async
),它将返回一个Job
实例,该实例唯一地标识协程并管理其生命周期。 正如我们在上面看到的,您还可以将Job
传递给CoroutineScope
以保持其生命周期。
协程上下文 (CoroutineContext)
The CoroutineContext
is a set of elements that define the behavior of a coroutine. It’s made of:
的 CoroutineContext
是定义协程行为的一组元素。 它是由:
Job
— controls the lifecycle of the coroutine.Job
—控制协程的生命周期。CoroutineDispatcher
— dispatches work to the appropriate thread.CoroutineDispatcher
—将工作分派到适当的线程。CoroutineName
— name of the coroutine, useful for debugging.CoroutineName
—协程的名称,可用于调试。CoroutineExceptionHandler
— handles uncaught exceptions, will be covered in Part 3 of the series.CoroutineExceptionHandler
—处理未捕获的异常,将在本系列的第3部分中介绍。
What’s the CoroutineContext
of a new coroutine? We already know that a new instance of Job
will be created, allowing us to control its lifecycle. The rest of the elements will be inherited from the CoroutineContext
of its parent (either another coroutine or the CoroutineScope
where it was created).
什么是新的协程的协程CoroutineContext
? 我们已经知道将创建一个新的Job
实例,从而使我们能够控制其生命周期。 其余元素将从其父级的CoroutineContext
(另一个协程或在其创建的CoroutineScope
中继承。
Since a CoroutineScope
can create coroutines and you can create more coroutines inside a coroutine, an implicit task hierarchy is created. In the following code snippet, apart from creating a new coroutine using the CoroutineScope
, see how you can create more coroutines inside a coroutine:
由于CoroutineScope
可以创建协程,并且您可以在协程内部创建更多协程,因此将创建隐式任务层次结构 。 在以下代码片段中,除了使用CoroutineScope
创建新的协程CoroutineScope
,还请参见如何在协程内部创建更多协程:
val scope = CoroutineScope(Job() + Dispatchers.Main)val job = scope.launch {
// New coroutine that has CoroutineScope as a parent
val result = async {
// New coroutine that has the coroutine started by
// launch as a parent
}.await()
}
The root of that hierarchy is usually the CoroutineScope
. We could visualise that hierarchy as follows:
该层次结构的根通常是CoroutineScope
。 我们可以将该层次结构可视化如下:

工作生命周期 (Job lifecycle)
A Job
can go through a set of states: New, Active, Completing, Completed, Cancelling and Cancelled. While we don’t have access to the states themselves, we can access properties of a Job: isActive
, isCancelled
and isCompleted
.
Job
可以经历以下一组状态:新建,活动,正在完成,已完成,正在取消和已取消。 尽管我们无权访问状态本身,但我们可以访问Job的属性: isActive
, isCancelled
和isCompleted
。

If the coroutine is in an active state, the failure of the coroutine or calling job.cancel()
will move the job in the Cancelling state (isActive = false
, isCancelled = true
). Once all children have completed their work the coroutine will go in the Cancelled state and isCompleted = true
.
如果协程处于活动状态,则协程失败或调用job.cancel()
将使作业处于取消状态( isActive = false
, isCancelled = true
)。 一旦所有孩子都完成了工作,协程将进入已取消状态,并且isCompleted = true
。
父CoroutineContext解释 (Parent CoroutineContext explained)
In the task hierarchy, each coroutine has a parent that can be either a CoroutineScope
or another coroutine. However, the resulting parent CoroutineContext
of a coroutine can be different from the CoroutineContext
of the parent since it’s calculated based on this formula:
在任务层次结构中,每个协程都有一个父级,该父级可以是CoroutineScope
或另一个协程。 但是,协程的最终父CoroutineContext
与父协程的CoroutineContext
不同,因为它是根据以下公式计算的:
Parent context = Defaults + inherited
CoroutineContext
+ arguments父上下文 =默认值+继承的
CoroutineContext
+参数
Where:
哪里:
Some elements have default values:
Dispatchers.Default
is the default ofCoroutineDispatcher
and“coroutine”
the default ofCoroutineName
.一些元素具有默认值:
Dispatchers.Default
是CoroutineDispatcher
默认值,“coroutine”
是CoroutineName
的默认值。The inherited
CoroutineContext
is theCoroutineContext
of theCoroutineScope
or coroutine that created it.继承
CoroutineContext
是CoroutineContext
中的CoroutineScope
创建它或协程。Arguments passed in the coroutine builder will take precedence over those elements in the inherited context.
在协程生成器中传递的参数将优先于继承上下文中的那些元素。
Note: CoroutineContext
s can be combined using the +
operator. As the CoroutineContext
is a set of elements, a new CoroutineContext
will be created with the elements on the right side of the plus overriding those on the left. E.g. (Dispatchers.Main, “name”) + (Dispatchers.IO) = (Dispatchers.IO, “name”)
注意 :可以使用+
运算符组合CoroutineContext
。 由于CoroutineContext
是一组元素, CoroutineContext
将创建一个新的CoroutineContext
,其中加号的右侧的元素会覆盖左侧的元素。 例如(Dispatchers.Main, “name”) + (Dispatchers.IO) = (Dispatchers.IO, “name”)

Now that we know what’s the parent CoroutineContext
of a new coroutine, its actual CoroutineContext
will be:
现在我们知道新协程的父CoroutineContext
是什么,其实际CoroutineContext
将为:
New coroutine context = parent
CoroutineContext
+Job()
新的协程上下文 =父级
CoroutineContext
+Job()
If with the CoroutineScope
shown in the image above we create a new coroutine like this:
如果使用上CoroutineScope
所示的CoroutineScope
,我们将创建一个新的协程,如下所示:
val job = scope.launch(Dispatchers.IO) {
// new coroutine
}
What’s the parent CoroutineContext
of that coroutine and its actual CoroutineContext
? See the solution in the image below!
该协程及其实际CoroutineContext
的父CoroutineContext
是什么? 请参见下图中的解决方案!

The resulting parent CoroutineContext
has Dispatchers.IO
instead of the scope’s CoroutineDispatcher
since it was overridden by the argument of the coroutine builder. Also, check that the Job
in the parent CoroutineContext
is the instance of the scope’s Job
(red color), and a new instance of Job
(green color) has been assigned to the actual CoroutineContext
of the new coroutine.
生成的父CoroutineContext
具有Dispatchers.IO
而不是作用域的CoroutineDispatcher
因为它已被协CoroutineDispatcher
器的参数覆盖。 此外,检查Job
父CoroutineContext
是示波器的情况下Job
(红色),和一个新的实例Job
(绿色)被分配给实际CoroutineContext
新协程。
As we will see in Part 3 of the series, a CoroutineScope
can have a different implementation of Job
called SupervisorJob
in its CoroutineContext
that changes how the CoroutineScope
deals with exceptions. Therefore, a new coroutine created with that scope can have SupervisorJob
as a parent Job
. However, when the parent of a coroutine is another coroutine, the parent Job
will always be of type Job
.
正如我们将在本系列的第3部分中看到的那样, CoroutineScope
可以在其CoroutineContext
中具有一个称为SupervisorJob
的Job
的不同实现,该CoroutineContext
改变了CoroutineScope
处理异常的方式。 因此,使用该范围创建的新协程可以将SupervisorJob
作为父Job
。 但是,当协程的父级是另一个协程时,父级Job
始终为Job
类型。
协程嵌套协程