协程中的所谓 结构化并发,就是指 父协程和子协程一起有组织有预谋地合作干活,如果不讨论scupervisorScope{}的话,大致上内涵如下:

  1. 子协程fail——报错,父协程和其他子协程都cancel.
  2. 子协程cancel,父协程和其他子协程没事
  3. 父协程fail,它所有子协程cancel。
  4. 父协程cancel,它所有子协程cancel。

子协程指CoroutineScope.launch/async调用的结果。为避免混乱,不讨论 coroutineScope\ supervisorScope

上面四个情况的案例

但也有例外,

当一个协程被其它协程在 CoroutineScope 中启动的时候, 它将通过 CoroutineScope.coroutineContext 来承袭上下文(除了Job会是一个新实例,其他的例如调度器都继承下来),并且这个新协程的 Job 将会成为父协程作业的子Job。当一个父协程被取消的时候,所有它的子协程也会被递归的取消。

However, this parent-child relation can be explicitly overriden in one of two ways:

  1. When a different scope is explicitly specified when launching a coroutine (for example, GlobalScope.launch), then it does not inherit a Job from the parent scope.
  2. When a different Job object is passed as the context for the new coroutine (as shown in the example below), then it overrides the Job of the parent scope.

In both cases, the launched coroutine is not tied to the scope it was launched from and operates independently.

第一种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
fun main() = runBlocking<Unit> {
// 启动一个协程来处理某种传入请求(request)
val request = launch {
// 一个TopLevelScope
CoroutineScope(context = Dispatchers.Default).launch {
println("NewTopLevelScope job1: I run in my own Job and execute independently!")
delay(1000)
println("NewTopLevelScope job1: I am not affected by cancellation of the request")
}
// 另一个则承袭了父协程的上下文
launch {
delay(100)
println("ChildCoroutine job2: I am a child of the request coroutine")
delay(1000)
println("ChildCoroutine job2: I will not execute this line if my parent request is cancelled")
}
}
delay(500)
request.cancel() // 取消请求(request)的执行
println("main: Who has survived request cancellation?")
delay(1000) // 主线程延迟一秒钟来看看发生了什么
}

结果
NewTopLevelScope job1: I run in my own Job and execute independently!
ChildCoroutine job2: I am a child of the request coroutine
main: Who has survived request cancellation?
NewTopLevelScope job1: I am not affected by cancellation of the request

Process finished with exit code 0

第二种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
fun main() = runBlocking<Unit> {
// 启动一个协程来处理某种传入请求(request)
val request = launch {
// 生成了两个子作业
launch(Job()) {
println("job1: I run in my own Job and execute independently!")
delay(1000)
println("job1: I am not affected by cancellation of the request")
}
// 另一个则承袭了父协程的上下文
launch {
delay(100)
println("job2: I am a child of the request coroutine")
delay(1000)
println("job2: I will not execute this line if my parent request is cancelled")
}
}
delay(500)
request.cancel() // 取消请求(request)的执行
println("main: Who has survived request cancellation?")
delay(1000) // 主线程延迟一秒钟来看看发生了什么
}

结果
job1: I run in my own Job and execute independently!
job2: I am a child of the request coroutine
main: Who has survived request cancellation?
job1: I am not affected by cancellation of the request

Process finished with exit code 0

要注意,无论如何,子协程的Job都是一个新实例,和Parent的Job不是同一个实例。我想区别在于 隐式创建时 带上了一些父子协程的信息。

参考资料:协程上下文和调度器