上一篇文章中我们分析了挂起函数的本质(状态机),以及查看编译器为我们生成的类里面是如何借用状态机实现的"挂起",那么在实际coding中我们该如何使用协程呢?
协程作用域
所有协程都必须在一个协程作用域CoroutineScope内运行。一个 CoroutineScope
管理一个或多个相关的协程。
协程如何开启呢?可以通过launch和async函数创建协程并将其函数主体的执行分派给相应的调度程序。
调度程序也就是launch和async的函数主体会运行在哪个线程中。比如
javascript
launch(Dispatchers.IO){
//协程主体函数
}
Dispatchers.IO
指示此协程应在为 I/O 操作预留的线程上执行。
那么当协程内部还需要切换线程的时候呢?典型的场景比如开启协程获取数据需要进行不同的线程切换: 这时候可以使用withContext
javascript
withContext(Dispatchers.IO) {
// IO线程运行
}
当withContext运行完成之后会自动恢复调用withContext的线程中。
如何看阻塞
同一协程体内的代码是阻塞的,但是协程体里面又开启了一个协程,两个协程不影响各自运行,但父协程取消的时候默认子协程也会取消。
取消协程运行
不管是通过launch还是async开启的协程,其都会返回一个Job对象,调用该Job对象的cancel即可取消协程。协程内部会通过抛出一个取消异常终止运行。
上面是针对某一个具体的协程取消,如果我想全部取消呢?比如界面销毁的时候,通过调用协程域的取消,位于这个协程域里面的协程都会取消。 Android为我们自动提供了一个viewmodelScope,他可以在配置改变引起的销毁重建时自动取消开启协程不用担心泄漏问题。
CoroutineContext
CoroutineContext
使用以下元素集定义协程的行为:代表该协程的上下文信息比如协程的名称,协程的调度器(Dispatcher),异常处理等
Job
:控制协程的生命周期。CoroutineDispatcher
:将工作分派到适当的线程。CoroutineName
:协程的名称,可用于调试。CoroutineExceptionHandler
:处理未捕获的异常。
协程如何测试
开启协程
runTest
是用于测试的协程构建器。相比于正式编码的时候使用的是launch和async。使用此构建器可封装包含协程的任何测试。
必须使用指定调度器
通过TestDispatchers调度器来指定协程的运行线程,相比于正式编码使用的是Dispatchers.IO,Dispatchers.Main,Dispatchers.Default。 除 runTest
创建的顶级协程外,如果代码还创建了新的协程,则需要选择适当的 TestDispatcher
,以控制这些新协程的调度方式,设置新协程的调度器。 TestDispatchers
是用于测试的 [CoroutineDispatcher
] 实现。由于多线程下的测试是不可预测的,所以正式编码中出现的withContext切换线程应换成单线程的方式因为这样将会使得结果可预测,所以再项目中不能使用硬编码去设置调度器Dispatchers,应该使用注入的方式如果是测试注入TESTDispatchers,如果是正式注入正常的Dispatcher。