假设我有一个按钮,它在Android中有一个监听器:
someView.setOnClickListener {
viewModel.doSomething()
}
这将为每次按下按钮创建一个新的Job。在我的ViewModel中,doSomething函数从viewModelScope中启动挂起函数:
fun doSomething() {
viewModelScope.launch {
doSomething1()
doSomething2()
doSomething3()
}
}
suspend fun doSomething1() {
delay(100)
Log.d("TEST", "doSomething1: 1 ")
}
suspend fun doSomething2() {
delay(300)
Log.d("TEST", "doSomething2: 2 ")
}
fun doSomething3() {
Log.d("TEST", "doSomething3: 3 ")
}
现在,如果这个按钮被快速地连续按下(理论上,假设我可以从侦听器调用函数两次,因此第一次调用的执行尚未完成),我将在我的logcat中得到以下结果:
D/TEST: doSomething1: 1 D/TEST: doSomething1: 1 D/TEST: doSomething2: 2 D/TEST: doSomething3: 3 D/TEST: doSomething2: 2 D/TEST: doSomething3: 3
我真正想要实现的是,如果我可以从同一个作用域启动doSomething()
两次,那么它将同步运行。
D/TEST: doSomething1: 1 D/TEST: doSomething1: 2 D/TEST: doSomething2: 3 D/TEST: doSomething3: 1 D/TEST: doSomething2: 2 D/TEST: doSomething3: 3
我如何实现这种行为,以便在开始相同的协程之前,第一个必须完成?
你可以使用互斥锁来做到这一点——无论你按了多少次按钮,锁都会保持后面的1-2-3序列不启动,直到前面的序列完成。
private val lock = Mutex()
fun doSomething() {
viewModelScope.launch {
// lock means only one "1-2-3" sequence can execute
// at a time, subsequent calls will suspend here and wait
// for the lock to be released before starting
lock.withLock {
doSomething1()
doSomething2()
doSomething3()
}
}
}
您也可以使用通道一次运行一个
private val channel = Channel<Job>(capacity = Channel.UNLIMITED).apply {
viewModelScope.launch {
consumeEach { it.join() }
}
}
fun doSomething() {
channel.trySend(
// send a lazily executed "1-2-3" job to the channel for it
// to run (will run jobs one at a time and wait for each
// job to complete before starting the next)
viewModelScope.launch(start = CoroutineStart.LAZY) {
doSomething1()
doSomething2()
doSomething3()
}
)
}
可以通过保存最后一个作业并在执行新的协程之前等待它完成来解决:
private var lastJob: Job? = null
fun doSomething() {
val prevJob = lastJob
lastJob = lifecycleScope.launch {
prevJob?.join()
doSomething1()
doSomething2()
doSomething3()
}
}