如何让协程仅在第一个完成时启动下一个作业
How to make coroutines launch next job only when first is finished
假设我有一个按钮,它在 Android 中有一个侦听器:
someView.setOnClickListener {
viewModel.doSomething()
}
这将在每次按下按钮时创建一个新作业。在我的 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
我怎样才能实现这种行为,以便在启动同一个协程之前,第一个协程必须完成?
可以通过保存最后一个作业并等待它完成再执行新协程来解决:
private var lastJob: Job? = null
fun doSomething() {
val prevJob = lastJob
lastJob = lifecycleScope.launch {
prevJob?.join()
doSomething1()
doSomething2()
doSomething3()
}
}
您可以使用互斥锁来执行此操作 - 无论您按下按钮多少次,该锁都会阻止后续的 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()
}
)
}
假设我有一个按钮,它在 Android 中有一个侦听器:
someView.setOnClickListener {
viewModel.doSomething()
}
这将在每次按下按钮时创建一个新作业。在我的 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
我怎样才能实现这种行为,以便在启动同一个协程之前,第一个协程必须完成?
可以通过保存最后一个作业并等待它完成再执行新协程来解决:
private var lastJob: Job? = null
fun doSomething() {
val prevJob = lastJob
lastJob = lifecycleScope.launch {
prevJob?.join()
doSomething1()
doSomething2()
doSomething3()
}
}
您可以使用互斥锁来执行此操作 - 无论您按下按钮多少次,该锁都会阻止后续的 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()
}
)
}