Lets say that I have a button, which has a listener in Android:
someView.setOnClickListener {
viewModel.doSomething()
}
This will create a new Job for each time that the button is pressed. In my ViewModel, the doSomething function is launching suspend functions from the 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 ")
}
Now, if this button gets pressed very quickly in succession (in theory, lets say i can call the function from the listener twice, so that the execution of the first call hasn't finished yet), I will get following result in my 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
What I actually want to achieve is, what would happen if I could launch doSomething()
twice from the same scope, so it runs synchronous.
D/TEST: doSomething1: 1 D/TEST: doSomething1: 2 D/TEST: doSomething2: 3 D/TEST: doSomething3: 1 D/TEST: doSomething2: 2 D/TEST: doSomething3: 3
How can I achieve that behaviour, so that before starting the same coroutine, the first one has to finish?
CodePudding user response:
You can use a Mutex lock to do this - the lock would keep subsequent 1-2-3 sequences from launching until the prior ones are complete, no matter how many times you push the button.
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()
}
}
}
You could also use a channel to run them one at a time
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()
}
)
}
CodePudding user response:
Can be solved by saving the last job and waiting for it to finish before executing a new coroutine:
private var lastJob: Job? = null
fun doSomething() {
val prevJob = lastJob
val currentJob = lifecycleScope.launch {
prevJob?.join()
doSomething1()
doSomething2()
doSomething3()
}
lastJob = currentJob
}