协程小记——取消正在耗时的任务

186 阅读3分钟

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

在这个系列中,我打算介绍一些协程的应用场景,希望能帮助大家理解协程。

你在开发可能会经常遇到这样的需求:你需要执行一个耗时函数,而它的结果没出现或者它在运行时就不打算要这个结果了,或者想要重新执行一次这个函数。

若你只是简单地重新执行了这个函数,这将会给你带来一些意想不到的惊喜 :)

对于上述场景,我可以举俩个开发中的例子。在用户想要填写一些信息的时候,我们需要在界面中适时地用一些鲜明的文字去提醒用户该做什么或者不该做什么,所以我们需要在界面中将提示停留3秒左右,当用户触发下一个提示时,这个提示应该变化;又或者应用中的语音播报,在它没读完的时候经常要触发下一个语音播报。

以上的示例,当你遇到时会怎么解决呢?在这里我将介绍一个基于协程的简单解决思路。首先我将情景一再现一下,如下图中红色文字是提示语,黑色按钮是触发提示语的行为。

image.png

按下一次按钮,会修改值,并展示俩秒。

MyViewModel.kt

val str = MutableLiveData<String>("")
var x = 1
fun change(){
    viewModel.lunch{
        str.postValue("${x++}")
        delay(2000)
        str.postValue("")
    }
}

当按钮2s内调用change()函数会出问题:第二次执行函数展示的结果,会第一次执行后2s被置空。问题很明显就出在第一次函数还在执行,最简单的解决方法就是让它在第二次执行的之后取消掉第一次执行。那这该怎么做呢?

那我得先介绍一下协程的一个重要概念————结构化并发。当一个异步函数A(此处对应的是协程中的挂起函数)嵌套着另一些异步函数B,而异步函数B套着异步函数C......这种俗称回调地狱的东西,一直是开发者和维护者的噩梦。当你想优化一下性能,在他们执行但不需要结果的时候停止他们,这更恐怖。然而结构化并发这一实现,让异步代码可以像同步代码一样写,这极大地降低了开发的痛苦。这里简单地说一下结构化并发的实现,主要靠suspend关键字和协程函数的可取消。

而本篇文章的思路就源自于协程的可取消,实际上android官方的协程指南中也推荐将协程设为可取消。

协程可取消的基础在于协程对挂起函数状态的一种监听。对于结构化的协程,取消第一个挂起函数,一系列调用的协程都会取消。正常挂起函数是没有可取消的,当你在挂起函数的合适位置写上一句

ensureActive()

那这个挂起函数就是取消的了。不过上面的示例不需要加这句,因为delay函数也有可取消的意义。 这时候我们来取消一个协程吧。 首先viewModel.lunch是一个job(协程任务),我们可以在change函数外部定义一个可变的job,在执行change函数一开始的时候我们先把job取消(不管它在不在执行),再继续执行就好。代码如下:

var job: Job? = null
fun change(){
    job?.cancelAndJoin()
    job = viewModel.lunch{
       // ...
    }
}

这样取消掉上次执行的协程函数,就不会出现问题了。