参考链接
示例来自bilibili Kotlin语言深入解析 张龙老师的视频
1 如何取消协程
import kotlinx.coroutines.*
/**
* 协程的取消
*/
fun main() = runBlocking {
// 得到启用的协程myJob
val myJob = GlobalScope.launch {
repeat(200) { i ->
println("hello $i")
delay(500)
}
}
delay(1100)
println("hello world")
/**
* Cancels this job with an optional cancellation cause.
* A cause can be used to specify an error message or to provide other details on the cancellation reason for debugging purposes.
* See Job documentation for full explanation of cancellation machinery.
*/
myJob.cancel(CancellationException("test exception"))// 取消协程 取消协程是一个正常的操作 因此实际上CancellationException不会被打印
/**
* Suspends the coroutine until this job is complete.
* 取消协程不是立马结束的 取消协程时 cancel和join经常成对出现
*/
myJob.join()
// myJob.cancelAndJoin() // 上面两句等价于cancelAndJoin
println("end")
}
class HelloKotlin {
}
2 协程取消的注意点
如果协程正处于某个计算过程中且没有检查取消状态,那么它就是无法被取消的
import kotlinx.coroutines.*
/**
* 协程取消的注意点
* 如果协程正处于某个计算过程中且没有检查取消状态,那么它就是无法被取消的
*
* kotlinx.coroutines包下所有的挂起函数都可以被取消
* 当调用取消方法时 他们会检查取消状态,当取消时抛出CancellationException异常
* 不过 如果协程正处于某个计算过程中且没有检查取消状态,那么它就是无法被取消的(可以理解为取消失败)
*/
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {// 取得启动的协程job
var nextTime = startTime
var i = 0;
while (i < 20) {// 在时间没有到达指定时间 该while循环一直在空轮转(while循环满足 而 if条件不满足)
if (System.currentTimeMillis() > nextTime) {// 每500毫秒 i++ 直到i>=20结束
println("job:I am sleeping ${i++}")
nextTime += 500
}
}
println("end while")
}
delay(1300)
println("hello world")
job.cancelAndJoin() // 取消协程 但是协程处于计算中 且协程没有检查取消状态 因此协程没有被取消 一直等待 直到协程执行完毕
println("welcome")
}
/**
输出如下
job:I am sleeping 0
job:I am sleeping 1
job:I am sleeping 2
hello world
welcome
job:I am sleeping 3
job:I am sleeping 4
job:I am sleeping 5
job:I am sleeping 6
job:I am sleeping 7
job:I am sleeping 8
job:I am sleeping 9
job:I am sleeping 10
job:I am sleeping 11
job:I am sleeping 12
job:I am sleeping 13
job:I am sleeping 14
job:I am sleeping 15
job:I am sleeping 16
job:I am sleeping 17
job:I am sleeping 18
job:I am sleeping 19
end while
*/
class HelloKotlin2 {
}
3 协程取消的注意点
如果协程正处于某个计算过程中 如何正确取消协程
import kotlinx.coroutines.*
/**
* 协程取消的注意点
* 如果协程正处于某个计算过程中 如何正确取消协程
*
* 有两种方式可以让协程能够被成功取消
* 1 周期性调用一个挂起函数 该挂起函数会检查取消状态 例如挂起函数delay
* 2 显示地检查取消状态
*
* 下面地示例采用第二种方式取消协程
*/
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {// 取得启动的协程job
var nextTime = startTime
var i = 0;
/**
* Returns true when the current Job is still active (has not completed and was not cancelled yet).
* Check this property in long-running computation loops to support cancellation:
* while (isActive) {
* // do some computation
* }
* isActive是CoroutineScope地一个扩展属性
* 它当协程仍然处于活跃状态(还没有完成且没有取消)返回true
* 在长期计算循环中检查该属性可以让协程能够被取消
*/
while (isActive) {// 与HelloKotlin2唯一不同地方
if (System.currentTimeMillis() > nextTime) {// 每500毫秒 i++ 直到i>=20结束
println("job:I am sleeping ${i++}")
nextTime += 500
}
}
println("end while")
}
delay(1300)
println("hello world")
job.cancelAndJoin() // 取消协程 但是协程处于计算中 但是它检查取消状态 因此协程立即被取消
println("welcome")
}
/**
输出:
job:I am sleeping 0
job:I am sleeping 1
job:I am sleeping 2
hello world
end while
welcome
*/
class HelloKotlin3 {
}
4 协程取消的注意点
如果协程正处于某个计算过程中 如何正确取消协程2
import kotlinx.coroutines.*
/**
* 协程取消的注意点
* 如果协程正处于某个计算过程中 如何正确取消协程2
*
* 有两种方式可以让协程能够被成功取消
* 1 周期性调用一个挂起函数 该挂起函数会检查取消状态 例如挂起函数delay
* 2 显示地检查取消状态
*
* 下面地示例采用第二种方式取消协程
* 这里我们实际上用的是HelloKotlin1的代码
*
* 对比HelloKotlin3 HelloKotlin3_1之所以可以成功取消协程 因为
* 1.cancel中修改状态
* 追踪cancel的实现
* 以JobSupport为例 按照顺序追踪 cancel cancelInternal cancelImpl makeCancelling 内部最终会修改协程isActive的状态
* 2.delay这个挂起函数检查state
* delay:
* Delays coroutine for a given time without blocking a thread and resumes it after a specified time.
* This suspending function is cancellable.
* If the Job of the current coroutine is cancelled or completed while this suspending function is waiting,
* this function immediately resumes with CancellationException.
* Note that delay can be used in select invocation with onTimeout clause.
* Implementation note: how exactly time is tracked is an implementation detail of CoroutineDispatcher in the context.
* Params:
* timeMillis - time in milliseconds.
* 延迟指定时间的协程而不会阻塞线程 并且在等待时间到达的时候 协程能够再次启动
* 该挂起方法是可以取消的
* 如果当前协程的job在挂起方法正在等待时被取消或完成
* 那么该方法会被立马继续并抛出CancellationException
*
* 总结一下cancel将isActive更改为false 并且挂起函数可以检测到状态变化/或者可以主动判断isActive的值
* 说到底 协程要能取消成功 需要感知到isActive状态的变化
*/
fun main() = runBlocking {
// 得到启用的协程myJob
val myJob = GlobalScope.launch {
repeat(200) { i ->
println("hello $i")
delay(500)
}
}
delay(1100)
println("hello world")
/**
* Cancels this job with an optional cancellation cause.
* A cause can be used to specify an error message or to provide other details on the cancellation reason for debugging purposes.
* See Job documentation for full explanation of cancellation machinery.
*/
myJob.cancel(CancellationException("test exception"))// 取消协程 取消协程是一个正常的操作 因此实际上CancellationException不会被打印
/**
* Suspends the coroutine until this job is complete.
* 取消协程不是立马结束的 取消协程时 cancel和join经常成对出现
*/
myJob.join()
// myJob.cancelAndJoin() // 上面两句等价于cancelAndJoin
println("end")
}
class HelloKotlin3_1 {
}
5 协程与finally执行顺序
/**
* 使用finally来关闭资源
* join 与 cancelAndJoin都会等待所有的清理动作完成之后才会继续往下执行
*/
fun main() = runBlocking {
val myJob = launch {
try {
repeat(100) { i ->
println("job sleeping $i")
delay(500)
}
} finally {
println("finally执行") // cancelAndJoin会等待finally执行完毕
}
}
delay(1300)
println("hello world")
myJob.cancelAndJoin()
println("end")
}
class HelloKotlin4 {
}
6 如果已经取消的协程 再调用挂起方法(如delay)不能成功调用
/**
* 如果已经取消的协程 再调用挂起方法(如delay)不能成功调用
*
* 对于下面的示例 当我们在协程的finally中调用挂起函数时 会导致出现CancellationException异常 原因在于运行着该
* 代码块的协程已经被取消了。通常情况下,这没有什么问题,因为大多数关闭动作(如取消一个job 关闭连接)都是瞬时的(非阻塞的)
* 并不需要使用挂起函数;然而,在极少数情况下,我们想要在取消的协程中进行挂起操作时,我们可以将代码放置到withContext(NonCancellable){}
* 代码块中,在这种结构中 我们实际使用了withContext函数与NoCancellable的上下文 我们在下一节演示withContext
*
* 以下的示例中在主线程中延时1300毫秒
* 同时协程在不断的等待输出
* 当主线程延时结束 取消协程 finally中的delay执行失败 之后的语句不再执行
*/
fun main() = runBlocking {
val myJob = launch {
try {
repeat(100) { i ->
println("job: I am sleeping $i")
delay(500)
}
} finally {
println("execute finally")
delay(1000)// 因为走到这里时协程已经取消 会抛出CancellationException异常 因此后面的代码都不执行了
println("finally code after delay")
}
}
delay(1300)
println("hello world")
myJob.cancelAndJoin()
println("end")
}
class HelloKotlin5 {
}
7 已经取消的协程 再调用挂起方法(如delay)需要使用withContext代码块
/**
* 已经取消的协程 再调用挂起方法(如delay)需要使用withContext代码块
*
* 当我们在协程的finally中调用挂起函数时 会导致出现CancellationException异常 原因在于运行着该
* 代码块的协程已经被取消了。我们想要在取消的协程中进行挂起操作时,我们可以将代码放置到withContext(NonCancellable){}
* 代码块中,在这种结构中 我们实际使用了withContext函数与NoCancellable的上下文
*/
fun main() = runBlocking {
val myJob = launch {
try {
repeat(100) { i ->
println("job: I am sleeping $i")
delay(500)
}
} finally {
//
/**
* withContext: Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result
*
* NonCancellable
* 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.
* 防止代码块中的代码被取消
* Use it like this:
* withContext(NonCancellable) {
* // this code will not be cancelled
* }
*/
withContext(NonCancellable) {// 注意与上一个demo对比差异
println("execute finally")
delay(1000)
println("finally code after delay")
}
}
}
delay(1300)
println("hello world")
myJob.cancelAndJoin()
println("end")
}
class HelloKotlin5_1 {
}
8 协程的超时
/**
* 协程的超时 设置withTimeout
* 我们在使用协程时,如果取消了协程,那么很大一部分原因在于协程的执行时间超过了指定时间:我们可以通过手工引用与协程对应的Job的
* 方式来启动一个单独的协程用于取消这个协程,不过Kotlin提供了一个内建的函数帮我们更方便的实现这一点(withTimeout)
*/
fun main() = runBlocking {
try {
withTimeout(1900) {
repeat(5) {// 修改为4 就不会抛出TimeoutCancellationException
i ->
println("hello $i")
delay(400)
}
}
} catch (ex: TimeoutCancellationException) {
}
withTimeout(1900){
repeat(5){// 修改为4 就不会抛出TimeoutCancellationException
i ->
println("hello $i")
delay(400)
}
}
}
class HelloKotlin6 {
}
9 协程的超时2
/**
* 协程的超时设置withTimeoutOrNull
* 由withTimeout函数所抛出的TimeoutCancellationException是CancellationException的子类,当CancellationException异常
* 抛出时,我们并未在控制台上看到异常信息,因为在取消的协程中,CancellationException被认为是一种协程完成的正常原因
* 既然CancellationException仅仅是一个异常而已 所有的资源都会以通常的方式来关闭,那么我们可以将相关的代码放到一个try...catch块中;
* try {
* withTimeout(xxx) {
* ...
* }
* }
* catch (ex: TimeoutCancellationException) {
* }
*
* 此外,相对于withTimeout Kotlin还提供了一个更便利的方法withTimeoutNotNull
* 从功能角度上看 他非常类似withTimeout,不过当超时发生时,它不会抛出CancellationException异常 而是直接返回null
*
*/
fun main() = runBlocking {
val res = withTimeoutOrNull(1900){
repeat(4){// 修改为4 就不会抛出TimeoutCancellationException
i ->
println("hello $i")
delay(400)
}
"haha" // 协程没有超时 则res会被赋值为“haha” 否则 res等于null
}
println("result is $res")
}
class HelloKotlin7 {
}