01. 基础
01. 基础
1. 协程作用域
协程作用域用于控制一个协程的生命周期。
1.1 协程作用域(CoroutineScope)
启动并控制一个协程,都必须创建CoroutineScope对象,并将CoroutineContext作为构造参数。
1.1.1 协程上下文(CoroutineContext)
协程上下文由以下元素定义了协程的行为
Job
Job控制协程的生命周期
Job的生命周期经历以下一系列状态:新建,活动,正在完成,已完成,正在取消,已取消
调用方法控制协程:
start() 启动与此Job相关的协程
cancel() 取消该Job相关的协程
join() 暂停当前协程,直到此Job完成
或是获取协程当前状态:
children() 返回子Job
isActive() 处于活动状态
isCancelled() 已被取消
isCompleted() 已完成
CoroutineDispatcher
将协程分配到指定的线程
Main 用于界面交互和执行快速工作
Default 适合主线程之外执行大量占用CPU资源的工作
IO 适合执行磁盘或网络IO
Unconfined
CoroutineName
对应线程的名称
CoroutineExceptionHandler
处理未捕获的异常
val exceptionHandler: CoroutineExceptionHandler =
CoroutineExceptionHandler { coroutineContext, throwable ->
Log.e("异常抛出",throwable.message.toString())
}
CoroutineScope(Dispatchers.IO).launch(exceptionHandler) {
launch {
launch {
throw Exception("作用域1")
}
}
}
父级协程上下文与子级协程上下文
如: 父级的CoroutineContext = Job()+Dispatcher.Main+CoroutineExceptionHandler实例
那么:子级的CoroutineContext 如果有参数Dispatchers.IO,就会覆盖父级对应的参数。
(Dispatchers.Main, “name”) + (Dispatchers.IO) = (Dispatchers.IO, “name”)
由于我们创建一个新协程总会返回一个新的Job实例,所以,父级与新协程的Job永远不会是同一个实例。
2. 协程的取消
通常我们会开启一个协程做CPU密集型任务,遇到一些情况,需要取消。 但是,在协程中,只有工作完成后才会被移入Cancelled状态。
2.1 如何让协程可被取消
2.1.1 挂起函数
如delay(),withConext()等
2.2.2 job.isActive或ensureActive()来检查
在工作中判断当前job是否在活动状态,如果不在就不执行 : job.isActive
或是使用ensureActive()函数,本质上也是判断是否为活动状态,如果不是,抛出CancellationException 异常
2.2.3 yieId()
它的第一个操作就是判断当前是否为isActive,如果否,抛出CancellationException 异常。
2.2 等待协程的结果
2.2.1 Job.join()
挂起一个协程直到当前协程完成。
2.2.2 Deferred.await()
协程完成时通过.await()返回结果
2.2.3 他们前后调用cancel()会发生什么
先cancel后join 会将当前协程挂起直到Job完成。
先join后cancel 没效果,因为job已经完成了。
先cancel后await await不会返回结果,因为该Job已经被取消了,并且会抛出CancellationException异常。
先await后cancle Job已经完成,cancle自然没效果。
2.3 不可取消的协程上下文 withContext(NonCancellable)
协程被取消时,我们可能需要关闭一些正在使用资源或是有一些记录,清理的代码需要添加。
这时候 ,我们需要捕获到cancel的异常然后做处理,并且在finall中关闭资源或是日志等
CoroutineScope(Dispatchers.IO).launch(exceptionHandler) {
var nextPrintTime = System.currentTimeMillis()
val scope1 = launch {
try {
var count=0
while (count<5){
ensureActive()
if(nextPrintTime<=System.currentTimeMillis()){
nextPrintTime+=1000
count++
Log.i("scope1","第$count 次“)")
}
}
}catch (e:CancellationException){
LogUtils.i(e.message)
}finally {
}
}
delay(1000)
scope1.cancel()
}
但是,由于该job已经被取消了,那么在该job中的挂起函数也是无效的,这时,就需要用到withContext(NonCancellable)
withContext(NonCancellable){
delay(1000L) // 或者其他挂起函数
println(“Cleanup done!”)
}
suspendCancellableCoroutine
将回调改为协程(改为挂起函数),可使用suspendCoroutine。 但是suspendCancellableCoroutine更好,他可以使用 continuation.invokeOnCancellation来完成取消后的工作。
suspend fun work() {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
// 可以做一些清理工作
}
// 其余的实现
}
suspendCancellableCoroutine使用示例
interface CallBack<T> {
fun onScuccess(t: T)
fun onFailed(e: Exception)
}
fun login(callBack: CallBack<String>){
callBack.onScuccess("登录成功")
}
suspend fun getResult():String = suspendCancellableCoroutine {
login(object :CallBack<String>{
override fun onScuccess(t: String) {
it.resume(t)
}
override fun onFailed(e: Exception) {
it.resumeWithException(e)
}
})
}
3. 协程的异常
协程中,出现异常时,会将它传播到它的父级。 这时, 父级会进行如下三步:
取消其余子协程。
取消自身。
将异常传播给他的父级。
所有的异常都必须被处理。
3.1 SupervisorJob
使用SupervisorJob,子协程的异常不会传播。
SupervisorJob只在SuperviserScope{}或CoroutineScope(SupervisorJob())创建的作用域下有效。
并且,它的CoroutineContext中没有CoroutineExceptionHandler。
CoroutineScope(Dispatchers.IO).launch(exceptionHandler) {
launch {
var i = 0;
while (i<10) {
delay(1000)
LogUtils.i("scope", "第${i++}次")
}
}
supervisorScope {
val supervisorJob = SupervisorJob()
withContext(supervisorJob){
try {
var i = 0;
while (true) {
if (i == 2) {
throw Exception("异常")
}
delay(1000)
LogUtils.i("supervisorScope", "第${i++}次")
}
}catch (e:Exception){
LogUtils.e("supervisorScope","捕获异常,但不传播")
}
}
}
val supervisorJob = SupervisorJob()
CoroutineScope(supervisorJob).launch {
try {
var i = 0;
while (true) {
if (i == 5) {
throw Exception("异常")
}
delay(1000)
LogUtils.i("supervisorScope2", "第${i++}次")
}
}catch (e:Exception){
LogUtils.e("supervisorScope2","捕获异常,但不传播")
}
}
val supervisorJob1 = SupervisorJob()
CoroutineScope(supervisorJob1).launch {
try {
var i = 0;
while (true) {
if (i == 4) {
throw Exception("异常")
}
delay(1000)
LogUtils.i("supervisorScope3", "第${i++}次")
}
}catch (e:Exception){
LogUtils.e("supervisorScope3","捕获异常,但不传播")
}
}
}
如上示例:
supervisorScope工作完成后,其他SupervisorJob才开始执行。
3.2 launch和async抛出异常
3.2.1 launch创建的协程作用域
由launch创建的可使用CoroutineExceptionHandler发送异常消息或try-catch捕获异常。
通过try-catch捕获异常不会传播。
CoroutineExceptionHandle 异常会传播,影响其他同级作用域与父级。
3.2.3 async创建的协程作用域
未能捕获异常,异常传播:
try {
async {
throw Exception()
}
}catch (e:Exception){
LogUtils.e("异常捕获")
}
捕获异常,异常传播:
try {
async {
throw Exception()
}.await()
}catch (e:Exception){
LogUtils.e("异常捕获")
}
协程内部捕获,不传播:
async {
try {
throw Exception()
}catch (e:Exception){
LogUtils.e("异常捕获")
}
}.await()
3.3 使用CoroutineExceptionHandler捕获,launch创建的,协程中的异常
观察异常的传播与捕获
val exceptionHandler: CoroutineExceptionHandler =
CoroutineExceptionHandler { coroutineContext, throwable ->
Log.e("异常抛出", throwable.message.toString())
}
findViewById<Button>(R.id.btn).setOnClickListener {
CoroutineScope(Dispatchers.IO+exceptionHandler).launch {
async {
throw Exception()
}.await()
launch {
while (true) {
delay(1000)
LogUtils.i("running")
}
}
}
}
4. 协程种不应被取消的操作
在Android中我们使用Jetpack提供的CoroutineScope:viewModelScope或lifecycleScope,当Activity、fragment、Lifecycle结束时,会取消正在进行的工作。
如果我们需要工作在未完成前不被取消,应该怎么做?
如果需要某个操作的生命周期长于app,那么就使用WorkManager, 例如向服务器发送日志。
如果时只需要app还活着,协程就一直运行,app杀死时就可以取消的操作,那么就使用协程。
class MyApplication : Application() {
//不需要取消该Scope,因为它会随着进程死亡而终止。
val applicationScope = CoroutineScope(SupervisorJob() + otherConfig)
}
使用launch或async启动协程
launch:
class Repository(
private val externalScope: CoroutineScope,
private val ioDispatcher: CoroutineDispatcher
) {
suspend fun doWork() {
withContext(ioDispatcher) {
doSomeOtherWork()
externalScope.launch {
//如果这里可能会抛异常,那么请用try.catch把这里包起来,或者定义一个CoroutineExceptionHandler在externalScope的CoroutineContext中
veryImportantOperation()
}.join()
}
}
}
async:
class Repository(
private val externalScope: CoroutineScope,
private val ioDispatcher: CoroutineDispatcher
) {
suspend fun doWork(): Any { // Use a specific type in Result
withContext(ioDispatcher) {
doSomeOtherWork()
return externalScope.async {
//调用await时会暴露异常,异常将在调用doWork的协程中传播。如果调用doWork处的协程已经cancel,把me该异常将被忽略。
veryImportantOperation()
}.await()
}
}
}