协程的取消与异常传递

160 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

前言

上一篇文章中,我们通过对协程一些基本概念的了解,对协程的简单使用有了初步的轮廓,本文将继续介绍Kotlin协程中几个比较常见的知识点,分别是协程中异常传递与取消。

正文

回调:

相信大家对回调这个词都不陌生,甚至天天跟它打交道,以下是使用回调的一个例子:

private fun callback1(id: Int,callback: ICallback<User>){
    requireUser(id, object : Callback<User> {
        override fun onSuccess(response: Response<User>) {
            if (response.body == null){
                callback.onFail(NullPointerException("response body is null"))
            }else{
                callback.onSuccessful(response.body)
            }
        }
        override fun onFail(e: Throwable) {
            callback.onFail(e)
        }
    })
}

调用的时候:

callback1(object : ICallback<User>{
    override fun onSuccessful(value: User) {
        mTvUserName.text = value.name
    }
​
    override fun onFail(e: Throwable) {
        Toast.makeText(this@MainActivity,e.message,Toast.LENGTH_SHORT).show()
    }
})

以上代码在平常开发中会经常碰到类似的,但是层级多了,就会加大理清回调的逻辑,很容易陷入回调地狱。这样存在的问题也很明显,当我们看写的代码是回调然后一层套一层,可以想象挺那个的。

接下来看看使用的协程是怎么样的:

private suspend fun callback2(): User{
    return suspendCancellableCoroutine<User> { coroutine ->
        requireUser(0, object : Callback<User> {
            override fun onSuccess(response: Response<User>) {
                if (response.body == null){
                    coroutine.resumeWithException(NullPointerException("response body is null"))
                }else{
                    coroutine.resume(response.body)
                }
            }
​
            override fun onFail(e: Throwable) {
                coroutine.resumeWithException(e)
            }
        })
    }
}

通过resume方法来给方法返回结果,或者通过resumeWithException方法来给调用传递异常。

调用的代码:

mMainScope.launch {
    try {
        val user = callback2()
        mTvUserName.text = user.name
    }catch (e: Throwable){
        Toast.makeText(this@MainActivity,e.message,Toast.LENGTH_SHORT).show()
    }
}

调用的代码只需launch一个协程,然后里边执行的代码是串行的,看起来就像是同步的,而且也去除掉了回调,可以切换线程,比如说callback2方法执行的是在IO线程中,那么它返回来后,由于mMainScope的调度器是指定在主线程的,所以回来的操作也是在主线程中。

异常传递:

异常的传递可以为双向传递和单向传递,双向传递是指子协程发生异常的时候,会向外传给父协程,父协程发生异常时则会结束掉子协程。

//子传给父
private suspend fun coroutineScope1(){
    Log.i(myTag,"start")
    
    try {
        coroutineScope {
            Log.i(myTag,"1")
​
            launch {
                Log.i(myTag,"2")
                throw Exception("test")
            }
​
            delay(1000)
            Log.i(myTag,"3")
        }
    }catch (e: Exception){
        Log.i(myTag,"4 ${e.message}")
    }
​
    Log.i(myTag,"end")
}

输出结果:

12-09 17:36:27.420 18061-18061/com.sendi.coroutinedemo I/MainActivity: start
12-09 17:36:27.430 18061-18061/com.sendi.coroutinedemo I/MainActivity: 1
12-09 17:36:27.590 18061-18061/com.sendi.coroutinedemo I/MainActivity: 2
12-09 17:36:27.780 18061-18061/com.sendi.coroutinedemo I/MainActivity: 4 test
12-09 17:36:27.780 18061-18061/com.sendi.coroutinedemo I/MainActivity: end
//父传给子
private suspend fun coroutineScope2(){
    Log.i(myTag,"start")
    try {
        coroutineScope {
            Log.i(myTag,"1")
​
            launch {
                try {
                    delay(1000)
                    Log.i(myTag,"2")
                }catch (e: Exception){
                    Log.i(myTag,"3 ${e.message}")
                }
            }
​
            delay(100)
​
            throw Exception("test")
        }
    }catch (e: Exception){
        Log.i(myTag,"4 ${e.message}")
    }
​
    Log.i(myTag,"end")
}

输出结果:

12-09 17:41:40.050 20082-20082/com.sendi.coroutinedemo I/MainActivity: start
12-09 17:41:40.050 20082-20082/com.sendi.coroutinedemo I/MainActivity: 1
12-09 17:41:40.260 20082-20082/com.sendi.coroutinedemo I/MainActivity: 3 Parent job is Cancelling
12-09 17:41:40.260 20082-20082/com.sendi.coroutinedemo I/MainActivity: 4 test
12-09 17:41:40.260 20082-20082/com.sendi.coroutinedemo I/MainActivity: end

取消:

取消则是协程在执行的时候,通过调用cancel或者是父协程进行取消,进而让协程停止执行。取消其实就是将状态变成取消的状态,具体还得看启动的协程是否会支持停止掉当前的协程。

private suspend fun cancel1(){
​
    Log.i(myTag,"start")
​
    mMainScope.launch {
​
        Log.i(myTag,"1")
​
        delay(100)
​
        Log.i(myTag,"2")
    }
​
    delay(100)
​
    mMainScope.cancel()
​
    Log.i(myTag,"end")
}

输出:

12-09 17:54:06.890 23255-23255/com.sendi.coroutinedemo I/MainActivity: start
12-09 17:54:06.970 23255-23255/com.sendi.coroutinedemo I/MainActivity: 1
12-09 17:54:07.100 23255-23255/com.sendi.coroutinedemo I/MainActivity: end
private suspend fun execute(){
    supervisorScope {
        val job = launch {
            val result = cancel2(1)
            Log.i(myTag,"result is $result")
        }
        delay(50)
        Log.i(myTag,"after delay")
        job.cancel()
    }
}
​
private suspend fun cancel2(i: Int): String{
​
        return suspendCancellableCoroutine<String> { coroutine->
            Log.i(myTag,"1")
            coroutine.invokeOnCancellation {
                Log.i(myTag,"cancel")
            }
            Log.i(myTag,"2")
            //todo:挂起操作
            mMainScope.launch {
                Log.i(myTag,"3")
                delay(200)
            }
        }
    }

输出结果:

12-09 18:24:42.680 6192-6240/com.sendi.coroutinedemo I/MainActivity: 1
12-09 18:24:42.680 6192-6240/com.sendi.coroutinedemo I/MainActivity: 2
12-09 18:24:42.700 6192-6192/com.sendi.coroutinedemo I/MainActivity: 3
12-09 18:24:42.730 6192-6239/com.sendi.coroutinedemo I/MainActivity: after delay
12-09 18:24:42.730 6192-6239/com.sendi.coroutinedemo I/MainActivity: cancel

结语

本次对协程的相关知识的介绍就到这里了,主要了解协程的取消顺序逻辑、异常传递过程,内容比较短。