異步任務中的從新進入(Reentrancy)

 

一個按鈕,點擊執行一個任務。咱們可能直接在它的 Click 事件中寫下了執行任務的代碼。瀏覽器

通常咱們無需擔憂這樣的代碼會出現什麼問題——可是,這樣的好事情只對同步任務有效;一旦進入了異步世界,這即是無盡的 BUG!網絡


 

從新進入(Reentrancy)

private void Button_Click(object sender, RoutedEventArgs e)
{
    DoSomething();
}

private void DoSomething()
{
    // 同步任務。
}

▲ 以上,在按鈕點擊事件中執行同步任務併發

上面的代碼,不管咱們在界面上多麼瘋狂地點擊按鈕,由於 UI 會在任務執行的過程當中中止響應,因此 DoSomething 只會依次執行(還會偶爾忽略一些)。這一般不會形成什麼問題,但若是 DoSomething 變成異步的 DoSomethingAsync(就像下面那樣),那麼狀況就變得不一樣了。app

private async void Button_Click(object sender, RoutedEventArgs e)
{
    await DoSomethingAsync();
}

private async Task DoSomethingAsync()
{
    // 異步任務。
}

▲ 以上,在按鈕點擊事件中執行異步任務異步

因爲任務執行的過程當中 UI 依然是響應的,DoSomethingAsync 會所以在每一次點擊的時候都進入。在異步任務結束以前從新進入此異步任務的過程,叫作從新進入(Reentrancy)。async

從新進入的五種方式

微軟在 Handling Reentrancy in Async Apps (C#) 一文中給出了從新進入的三種方式:ide

  1. 禁用「開始」按鈕
  2. 取消和重啓操做
  3. 運行多個操做並將輸出排入隊列

從語言描述中就能知道除了第 2 點看起來具備通用性外,其餘兩點只爲了解決文章中面臨的「輸出網頁列表」問題。第 1 點其思想能夠重用,但第 3 點就很難抽取公共的從新進入思想。因而,我總結其前兩點,再額外補充兩種從新進入的方式,和不處理一塊兒做爲五種不一樣的處理方法。ui

  • 禁用從新進入
  • 併發
  • 取消而後重啓操做
  • 將異步任務放入隊列中依次執行
  • 僅執行第一次和最後一次

禁用從新進入

禁用是最直接最簡單也最完全的從新進入問題解決辦法。spa

Button.IsEnabled = false;
await DoSomethingAsync();
Button.IsEnabled = true;

既然從新進入可能出問題,那咱們就禁止從新進入好了……code

併發

固然,不處理也是一種方法。這意味着咱們須要真的考慮 DoSomethingAsync 併發形成的影響。

取消而後重啓操做

取消,而後從新執行一次,這也是常見的從新進入類型。瀏覽器或者資訊類 APP 中的刷新功能就是這種從新進入方式最多見的應用場景,用戶從新執行一次刷新,可能由於前面那一次(由於網絡問題或其餘緣由)太慢,因此從新開始。

將異步任務放入隊列中依次執行

放入隊列中是由於此異步任務的順序是很重要的,要求每一次執行且保持順序一致。典型的應用場景是每一次執行都須要獲取或生成一組數據輸出(到屏幕、文件或者其餘地方)。

僅執行第一次和最後一次

若是用戶每一次執行此異步任務都會獲取當前應用程序的最新狀態,而後根據最新狀態執行;那麼若是狀態更新了,對舊狀態執行多少次都是浪費的。

好比保存文件的操做。第一次進入異步任務的時候會進行保存,若是保存過程沒有結束又觸發新的保存,則等上一次保存結束以後再執行保存操做便可。而若是第一次保存沒有結束的時候又觸發很是屢次的保存,也只須要在第一次結束以後再保存一次便可,畢竟既然最後一次保存時的狀態已是最新狀態,不須要再把以前舊的狀態保存一次。


參考資料

相關文章
相關標籤/搜索