这里主要介绍取消协程的执行,以及超过一定超时时间之后结束协程
取消协程执行
在小型程序中,可以利用main
函数的结束来隐式结束所有子协程,
但在大型、长时间运行的程序中,我们就需要更细致的控制协程的生命周期,
launch
函数返回的 Job
对象可以用来取消运行中的协程:
输出结果如下:
上述例子,在 main
函数中调用 Job.cancel
方法来结束子协程的运行。
取消操作应具有交互性
协程的取消应该具有交互性,即应该在协程中处理协程被取消的情况,
在 kotlinx.coroutines
库中的挂起函数都是可取消的,
函数中会检查协程的取消并在协程取消时抛出 CancellationException
异常。
但是,如果协程正处在循环计算中并且没有检查是否取消了协程,
则 job.cancel
方法无法取消协程的执行,如下:
输出结果如下:
可以看出,子协程在调用 job.cancel
方法后并没有停止执行,
而是在 main
函数执行结束时才退出
取消循环运算代码
有两种办法可以用来解决上述问题:
- 周期性的调用挂起函数,如使用
yield
函数
- 显式的检查协程的运行状态
这里我们尝试一下第二种方法,在上述例子中使用 while(isActive)
来替换 while(i < 10)
输出结果如下:
这里我们可以看到 job.cancel
方法成功的结束了协程,
isActive
属性属于 CoroutineScope
接口,
可以在协程代码中访问,用来表示协程的运行状态。
使用 finally 关闭资源
kotlinx.coroutine
库中的挂起函数在取消时,
都会抛出 CancellationException
异常,
这个异常可以通过正常的 try ... catch ... finally
语句处理,
或者对于 use
函数,在协程取消时,也可以正常的执行其终止操作
输出如下:
执行不可取消代码块
如果在上述例子中的 finally
块中执行挂起函数,会导致新的
CancellationException
异常抛出,毕竟执行这块代码的协程已经处于取消状态了,
正常情况下,在 finally
块中执行的关闭操作都应该是非阻塞函数
(如关闭文件、取消任务或者关闭各种通信通道);
但在某些特殊情况下我们需要在取消的协程中执行挂起函数,
我们可以使用 run
函数与 NonCancellable
上下文包裹所需执行的代码块:
超时
实际操作中取消协程的最常见原因就是协程执行超时,
即协程的执行时间超过了设定的超时时间,根据上面学习的内容,
我们可以通过检查协程执行的时间,然后调用 Job
相应的方法来取消协程的执行,
不过,kotlinx.coroutine
库中提供了一个封装好的函数 withTimeout
输出结果如下:
上例输出中我们可以看出,在协程执行超时 withTimeout
函数抛出了
TimeoutException
,超时异常是 CancellationException
的一个私有子类,
在之前取消协程执行,抛出 CancellationException
异常的例子中,
并没有在控制台输出中看到错误堆栈的信息,但是这次超时异常中却出现了,
这是因为之前在协程中抛出 CancellationException
异常可以看做是
协程完成的正常原因,而上例中直接在 main
函数中调用 withTimeout
函数,
而不是在协程中,所以其抛出的异常就会输出在控制台中。
由于取消操作只是抛出了异常,所以所有的资源都会以正常的方式被关闭,
我们可以将 withTimeout
代码放在 try ... catch(e: CancellationException) ...
中,这样就可以在协程超时之后做一些相应的处理,如记录日志等。
总结
- 可以调用
job.cancel
方法来取消协程的执行,但循环计算代码不会被取消
kotlinx.coroutine
库中的挂起函数在协程被取消时会抛出 CancellationException
异常
- 在协程中可以根据
isActive
属性来判断协程运行状态,来结束不能正常取消的循环代码
run(NonCancellable)
函数可以用来执行不会被取消的代码块
withTimeout
函数可以用来执行指定超时时间的协程
参考链接
https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md#cancellation-and-timeouts