Concurrent? What the hell? That’s what backend developers think about! Or maybe not.
Imagine a scenario where there is a list sort button that toggles between ascending and descending order and then clicks to send the request. If the user clicks the button quickly and frequently, the result will occasionally be wrong. Notice the word “occasionally”, you read that right, occasionally you get the wrong sort, other times you get the right sort. This kind of question is the goblin that grinds people.
So why is it sometimes wrong? In fact, in this case, the final result is not “the result of the last sort performed”, but “the result of the last sort completed” (note the difference). More specifically, this error can be caused if the last ascending (or descending) request was sent and there is still a descending (or ascending) request remaining at the end of the last request.
Simulation code
Class ConcurrencyVM(application: application) : AMViewModel(application) {private val _sortedProducts = MutableLiveData<String>() val sortedProducts: LiveData<String> = _sortedProducts fun loadSortProducts(ascending: Boolean) { viewModelScope.launch { _sortedProducts.value = sortedProducts(ascending) } } private suspend fun sortedProducts(ascending: Boolean): String {delay(Random().nextint (4) * 1000L) return if (ascending) "else" "descending"}} // Use case code Use a text to display the sorting result, and use a CheckBox text to indicate the current execution status... vm.sortedProducts.observe(this, Observer { view.text.text = it }) view.checkbox.setOnCheckedChangeListener { buttonView, IsChecked -> if(isChecked){buttonView.text = "Execute ascending request" vm.loadsortProducts (true)}else{buttonView.text = "execute descending request" vm.loadSortProducts(false) }} ...Copy the code
Check whether the result of the CheckBox text corresponds to the result of the CheckBox text. The probability of a rough error is 25%, for the following reasons:
}else{if(the last completed request is the same as the last sent request){//25% (the last completed request is the same as the last sent request) Because the most completed request has the same purpose as the last request sent}{//25% this condition is incorrect, i.e. the last completed request has the same purpose as the last request sent (i.e. the parameters are different, one is in ascending order and the other is in descending order)}}Copy the code
The solution
1. Flag bit
That is, after the request is sent, the processing flag bits are checked in the method to achieve control.
For example, for button clicking, disable the button after clicking, or flag bit like isDoing.
This way in some concurrent often work in the scene, but may not work in some scenarios, such as in a very short period of time quickly click on the button (button click jitter), even with flags, when switching flag bit value has issued multiple execution (similar to the singleton implementation pattern + synchronous dual detection in lock or appear likely problem).
2, the queue
That’s right. Line up. One by one.
Queues are often very effective in concurrent request problems because they allow the order in which requests are sent to be consistent with the order in which they are processed to ensure the results are correct.
Because this approach allows requests to be executed sequentially, it can be a waste of resources in some scenarios. For example, with frequent refresh requests, all refreshes are executed the same and the result is the same, in which case the user only wants a valid result. Therefore, other processing strategies can be attached to the queue, such as processing the same type of behavior together or unqueuing, depending on the scenario.
3. Cancel the previous task
Cancel the previous task before starting the next one.
In the above example, when the user clicks the sort button once, it means that the previous sort request can be cancelled, after all, the result of the previous request is meaningless. Right?
This approach may not be suitable for global singletons because unrelated requesters should not cancel each other.
4. Join the previous task
In contrast to the third method, if a new request can reuse an existing request, this is a very good way to handle tasks that are already executing or waiting to be executed. For example, in a refresh scenario, there is no need to perform a new refresh task if a refresh task is already being executed. Of course, this approach does not work for the previous sorting scenario.
5 or more
There are more methods, such as a combination of the previous methods, queue + join the previous task, or queue + strategy (such as queuing tasks of the same type, etc.).
In general, there is no one-size-fits-all solution, and the methods used should be selected and varied according to specific scenarios.