NonCancellable其实是一个特殊的Job,看它的注释:

A non-cancelable job that is always active. It is designed for withContext function to prevent cancellation of code blocks that need to be executed without cancellation.

(设计目的是让withContext块在取消时也可以运行)

Use it like this:
withContext(NonCancellable) {
// this code will not be cancelled
}

WARNING: This object is not designed to be used with launch, async, and other coroutine builders. if you write launch(NonCancellable) { … } then not only the newly launched job will not be cancelled when the parent is cancelled, the whole parent-child relation between parent and child is severed. The parent will not wait for the child’s completion, nor will be cancelled when the child crashed.

换言之,不建议在launch async上用——因为它破坏了结构化并发

正常来说,

  1. 在进入withContext块之前,如果已经cancel了,那么不会进入
  2. 如果已经进入了,然后被cancel,在块中有delay这种可取消挂起函数,会抛出cancellationException结束withContext
  3. 如果在withContext结束返回值的时候检测到取消,会丢弃返回值并抛出cancellationException

那么NonCancellable可以帮助它:

  1. 如果已经cancel了,那么依然进入
  2. 如果已经进入了,然后被cancel,在块中有delay这种可取消挂起函数,忽略
  3. 如果在withContext结束返回值的时候检测到取消,忽略

换言之,不参与结构化并发。

其实不用NonCancellable,自己显示定义一个Job()也可以做到。

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 {
//sampleStart
val job = launch(Dispatchers.IO) {
try {
repeat(2) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
} finally {
cancel()
withContext(NonCancellable) { // NonCancellable / Job()
println("job: I'm running finally")
delay(1000L)
println("job: And I've just delayed for 1 sec because I'm non-cancellable")
}
}
}
delay(1500) // 延迟一段时间
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消该作业并等待它结束
println("main: Now I can quit.")
//sampleEnd
}

job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm running finally
main: I'm tired of waiting!
job: And I've just delayed for 1 sec because I'm non-cancellable
main: Now I can quit.