2014-03-28 28 views
3

我想知道哪種方法在內存和資源使用方面會更有效率。使用任務重試異步函數 - 哪種方法更高效?

特別是在方法#1中,我很難想象如何創建任務對象並且線程開始運行?有人可以解釋一下在封面下詳細描述的內容嗎?

我想使用#1,如果兩者之間沒有區別(想避免冒泡異步)。 #2,我知道編譯器會在下面生成一個狀態機並返回。 OTOH,#1在概念上看起來是遞歸的,但它是否會像傳統意義上的遞歸一樣在一個堆棧幀中等待另一個呢?

方法1:

internal static Task ExecuteAsyncWithRetry(Func<Task> methodToExecute, Func<bool> shouldRetry) 
    { 
     var tcs = new TaskCompletionSource<object>(); 

     try 
     { 
      return methodToExecute().ContinueWith<Task>((t) => 
      { 
       if (t.IsFaulted || t.IsCanceled) 
       { 
        if (shouldRetry()) 
        { 
         return ExecuteAsyncWithRetry(methodToExecute, shouldRetry); 
        } 
        else 
        { 
         tcs.SetException(t.Exception); 
        } 
       } 
       else 
       { 
        tcs.SetResult(null); 

       } 

       return tcs.Task; 
      }, TaskContinuationOptions.ExecuteSynchronously).Unwrap(); 
     } 
     catch(Exception ex) 
     { 
      tcs.SetException(ex); 
     } 

     return tcs.Task; 
    } 

方法#2(忽略了兩者之間的異常傳播的差異):

internal static async Task ExecuteWithRetry(Func<Task> methodToExecute, Func<bool> shouldRetry) 
    { 
     while (true) 
     { 
      try 
      { 
       await methodToExecute(); 
      } 
      catch(Exception ex) 
      { 
       if(!shouldRetry()) 
       { 
        throw; 
       } 
      } 
     } 
    } 
+0

第二種方法更清楚地閱讀和理解。我的經驗法則:如果可能,請使用異步等待。在你的情況下,這是可能的。但在第二種情況下,你錯過了某種終止條件。如果一切順利,'methodToExecute()' 將被重複執行。 – Krumelur

+0

請注意,該等待會丟失錯誤信息,因爲它解開了AggregateException的錯誤信息。對於像這樣的通用輔助方法來說這是不可取的。這不是解釋例外的行爲。 – usr

+0

謝謝@usr,但是,您能澄清一下丟失錯誤信息部分嗎?不應該等待UnWrap()的行爲並傳播內部任務的錯誤/異常? – Kakira

回答

3

除了不同的異常和取消的傳播,還有一個重要的區別。

在第一種情況下,由於TaskContinuationOptions.ExecuteSynchronously,您的延續在任務已完成的同一個線程上運行。

在第二種情況下,它將在原始同步上下文上運行(如果在具有同步上下文的線程上調用methodToExecute)。

雖然第一種方法可能更有效,但它也可能很難理解(特別是當你或其他人在一年後返回時)。

我會按照KISS principle,並與第二個堅持,有一個修正:

await methodToExecute().ConfigureAwait(false); 

更新解決註釋:

「OTOH,#1似乎遞歸的概念但它是否會在傳統意義上遞歸,如同一個堆棧幀等待另一個?「

#1,它是否會在同一堆棧幀遞歸發生,或異步不同的堆棧幀,完全取決於發生了什麼事情裏面methodToExecute。在大多數情況下,如果您在methodToExecute內部使用一些自然異步API,則不會有傳統遞歸。例如,HttpClient.GetStringAsynccompletes on a random IOCP pool threadTask.Delay在隨機工作者池線程上完成。

但是,即使是異步API也可能在同一個線程上同步完成(例如MemoryStream.ReadAsyncTask.Delay(0)),在這種情況下會有遞歸。

或者,使用TaskCompletionSource.SetResult裏面的methodToExecute也可能會觸發同步延續。

如果你真的想避免遞歸的可能性,呼籲通過Task.RunmethodToExecute(或Task.Factory.StartNew/Task.Unwrap)。或者,更好的是,刪除TaskContinuationOptions.ExecuteSynchronously

對於#2,即使在初始線程中存在同步上下文,也可以使用相同的方案。

+0

當然,謝謝你的回答,我也知道他們的不同之處。然而,我希望通過這些細微差別來回答我個人對概念的理解/鞏固。特別是#1,我想知道下面發生了什麼。 – Kakira

+0

@Kakira,現在我確定遵循。如果你自己想出了這個代碼,你應該可以理解它是如何工作的。如果不是,具體是什麼部分你不明白?僅供參考,[相關問題](http://stackoverflow.com/q/21345673/1768303)。 – Noseratio

+0

就像我說過的(不用擔心,這是我的代碼),我試圖想象下面會發生什麼。我專門試圖理解我的問題的這一部分:「OTOH,#1在概念上似乎是遞歸的,但它是否會像傳統意義上的遞歸那樣遞歸,就像在等待另一個棧幀一樣?」 – Kakira