沒有要開車,參賽規定有寫不能污言穢語,等我有空再去其他平台寫個開車系列的coroutine
這裡給個快轉,android開發者從1開始看,ktor從2開始看
如標題所訴,有時在客戶端會因為對方重複點擊按鈕,在結果還沒出來就發多一次請求,導致最後結果出錯,就壞掉了 就有bug了
而實際上發生什麼事呢? 預期的工作順序應該是
但在多次請求的時候,將不是回傳排序結果
,而是最後一個排序工作完成的結果
,中間大概就是divide and conquer的過程一直被打亂,導致結果不如預期
而這其實跟排序的邏輯沒有關係,排序的邏輯是正確的,也和coroutine本身沒有關係,在任何多執行緒的程式,都有可能發生這個問題,而其實只是沒有妥善處理觸發機制
那它其實有四種解法
對的,最簡單也最方便的方法就是在過程中讓按鈕不能按,這種設計其實很常見,限購產品呀、無效登入呀、防止洗版等等,(當然前端後端都擋是最好的),而我們通常會設一個條件,讓按鈕變成可按
而且這個方法的優點不只解決我們的問題,測試寫起來也方便
viewModelScope.launch{
_sortButtonsEnabled.value = false
try{
sortByAlphabet()
} catch(e:Exception){
} finally {
_sortButtonsEnabled.value = true
}
}
這邊用liveData示範,非常簡單卻又實際的解法
這邊提醒一個小細節,在launch預設是在main執行,這裡也充分利用這個特性,如果你切換了dispatcher,有可能disenable的速度感不上某些使用者的觸發
------嗶嗶,難易度分隔線,如果上述方法已經解決問題,可以有空了在了解其他解法,以ktor來說,直接來後面找答案吧,後端又沒按紐-----
由大大提供的gist,剩下的要搭配著看
有時我們在某個條件達成後,會執行某個動作,而我們永遠以最新的要求為主,所以會取消前面的請求
跟著大大寫起來就會是這樣
var controlledRunner = ControlledRunner<List<ProductListing>>()
...
suspend fun ...
{
return controlledRunner.cancelPreviousThenRun {
//someThing
}
}
而這個cancelPreviousThenRun和ControlledRunner不是原生的,都在那個gist裡面,我這邊簡單介紹一下,為什麼他能確保取消前一個病執行最新的任務呢
在他的cancelPreviousThenRun裡面是這麼寫的
suspend fun cancelPreviousThenRun(block: suspend() -> T): T {
// fast path: if we already know about an active task, just cancel it right away.
activeTask.get()?.cancelAndJoin()
而這個activeTask是
private val activeTask = AtomicReference<Deferred<T>?>(null)
AtomicReference翻過來應該叫原子性引用,而這個原子性保證了在多個線程中修改,不會使結果不一致
直接開門,不然又要講很多 AtomicReference原子性引用
Important: This pattern is not well suited for use in global singletons, since unrelated callers shouldn’t cancel each other.
這個方式會以舊的請求為主,畢竟如果工作一樣,何必再多做新的呢?
比如,對同個api發出5次請求,你明知每次都會拿到一樣的結果,何必讓客戶端做重工
var controlledRunner = ControlledRunner<List<ProductListing>>()
...
suspend fun ... {
return controlledRunner.joinPreviousOrRun {
//something
}
}
那他是如何做到放棄新請求的
suspend fun joinPreviousOrRun(block: suspend () -> T): T {
// fast path: if there's already an active task, just wait for it and return the result
activeTask.get()?.let {
return it.await()
}
...
}
聽著困難,但其實大家應該都寫過了,就是如果activeTask存在,就return它
val singleRunner = SingleRunner()
suspend fun ... {
return singleRunner.afterPrevious {
//someThing
}
}
如果你希望每個任務都會發生,但是要照請求的順序執行,大大的SingleRunner是以mutex去實作
mutex是一個鎖,文檔,你可以想像成水上樂園的滑水道,會有一個人在把關,等前一個人出了滑水道(任務結束),再讓下一個排隊的人進滑水道,代碼長這樣,mutex會在return時自動釋放
mutex.withLock {
return block()
}
mutex有lock和unlock,而withLock幾本上就是取代
mutex.lock(); try { …… } finally { mutex.unlock() }