2015-08-25 46 views
0

我想執行一個重試任務,該任務採用先前的失敗任務操作並重復執行。重試以前的任務動作TPL

這是我到目前爲止。然而,它只是重複了任務錯誤而不是實際再次開始任務動作的事實。

public static async Task<T> Retry<T>(this Task<T> task, int retryCount, int delay, TaskCompletionSource<T> tcs = null) 
{ 
    if (tcs == null) 
    { 
     tcs = new TaskCompletionSource<T>(); 
    } 

    await task.ContinueWith(async _original => 
    { 
     if (_original.IsFaulted) 
     { 
      if (retryCount == 0) 
      { 
       tcs.SetException(_original.Exception.InnerExceptions); 
      } 
      else 
      { 
       Console.WriteLine("Unhandled exception. Retrying..."); 

       await Task.Delay(delay).ContinueWith(async t => 
       { 
        await Retry(task, retryCount - 1, delay, tcs); 
       }); 
      } 
     } 
     else 
      tcs.SetResult(_original.Result); 
    }); 
    return await tcs.Task; 
} 

我試圖用一點反思來得到這個動作。但是,似乎一旦任務完成,操作就會設置爲空。

var action = task 
    .GetType() 
    .GetField("m_action", BindingFlags.NonPublic | BindingFlags.Instance) 
    .GetValue(task) as Action; 

理想情況下,我想我的實現看起來像這樣:

try 
{ 
    await MakeFailure().Retry(5, 1000); 
} 
catch (Exception ex) 
{ 
    Console.WriteLine("I had an exception"); 
} 

這是不可能的,但我想的代碼重構到Retry(Func<T> task)

+0

你對抗不使這是一個擴展方法,改變方法簽名爲'靜態異步任務重試(Func鍵> taskFactory,INT RetryCount重,...'? –

+0

不完全反對這麼做,但是它改變了代碼流向錯誤第一個佈局,我不喜歡'''Retry(async()=> await MakeFailure(),5,1000)'''不像'''等待MakeFailure()乾淨。 Retry(5,1000)''' –

+3

不要試圖破解TPL來做一些不適合的事情。在它上面創建一個抽象。 – i3arnon

回答

2

不完全反對它。但它會將代碼流轉換爲我不喜歡的錯誤第一個佈局

請考慮您的類型。 Task代表一個異步操作。在異步世界中,Task代表已啓動的異步操作Task不是你可以「重試」的東西。

另一方面,Func<Task>代表可以啓動的異步操作。或重新啓動。這就是你需要處理的事情。

一旦你使用了正確的類型,代碼很簡單:

public static async Task<T> Retry<T>(Func<Task<T>> action, int retryCount, int delay) 
{ 
    while (retryCount > 0) 
    { 
    try 
    { 
     return await action().ConfigureAwait(false); 
    } 
    catch (Exception ex) 
    { 
     await Task.Delay(delay).ConfigureAwait(false); 
     --retryCount; 
    } 
    } 
    return await action().ConfigureAwait(false); 
} 

像其他回答者,我建議你使用實際上是專爲這個圖書館。 Transient Fault Handling Application BlockPolly是兩個很好的例子。

+0

使用TPL時,我同意這是最好的答案。我希望一項任務保留了一些關於它正在處理或正在處理的行動的細節,但似乎沒有。 –

+0

@JonathanSheely:在現代('async' /'await')代碼中,大多數任務甚至沒有*操作。 –

1

有之前,爲了確保是一個非常棒的圖書館,你可以在不編寫自己的代碼的情況下利用它。它被稱爲瞬態故障應用程序塊。但是我首先要評估一個名爲TransientFaultHandling.Core的塊中的單個庫。

它的使用方式與上面的代碼非常相似。這裏有一個簡單的例子:

using System; 
using Microsoft.Practices.TransientFaultHandling; 

namespace Stackoverflow 
{ 
    class Program 
    { 
     internal class MyTransientErrorDetectionStrategy : ITransientErrorDetectionStrategy 
     { 
      public bool IsTransient(Exception ex) 
      { 
       return true; 
      } 
     } 

     private static void Main(string[] args) 
     { 
      const int retryCount = 5; 
      const int retryIntervalInSeconds = 1; 

      // define the strategy for retrying 
      var retryStrategy = new FixedInterval(
       retryCount, 
       TimeSpan.FromSeconds(retryIntervalInSeconds)); 

      // define the policy 
      var retryPolicy = 
       new RetryPolicy<MyTransientErrorDetectionStrategy>(retryStrategy); 

      retryPolicy.Retrying += retryPolicy_Retrying; 

      for (int i = 0; i < 50; i++) 
      { 
       // try this a few times just to illustrate 

       try 
       { 
        retryPolicy.ExecuteAction(SomeMethodThatCanSometimesFail); 

        // (the retry policy has async support as well) 
       } 
       catch (Exception) 
       { 
        // if it got to this point, your retries were exhausted 
        // the original exception is rethrown 
        throw; 
       } 
      } 

      Console.WriteLine("Press Enter to Exit"); 

      Console.ReadLine(); 
     } 

     private static void SomeMethodThatCanSometimesFail() 
     { 
      var random = new Random().Next(1, 4); 

      if (random == 2) 
      { 
       const string msg = "randomFailure"; 

       Console.WriteLine(msg); 

       throw new Exception(msg); 
      } 
     } 

     private static void retryPolicy_Retrying(object sender, RetryingEventArgs e) 
     { 
      Console.WriteLine("retrying"); 
     } 
    } 
} 
+0

儘管概念是完善的,但我發現API的詳細特性非常瑣碎框架。好的回答不錯 –

1

,你必須是,一旦你在飛行中有一個Task<T>它不能被撤消或重試的問題。您必須以Func<Task<T>>開頭才能重試。

現在,您可以直接使用TPL,但我建議使用Microsoft的Reactive Framework來構建所需的功能。它比TPL更強大。

給定一個Func<Task<T>>這是你需要的東西:

var i = 0; 

Func<Task<int>> taskFactory =() => Task.Run(() => 
{ 
    if (i++ == 0) 
     throw new Exception("Foo"); 
    return i; 
}); 

int retryCount = 5; 
Task<int> retryingTask = Observable.FromAsync(taskFactory).Retry(retryCount).ToTask(); 

Console.WriteLine(retryingTask.Result); 

的無功框架將讓你做這麼多 - 這是一個非常強大的庫:

Func<Task<T>> taskFactory = ... 
int retryCount = 5; 
Task<T> retryingTask = Observable.FromAsync(taskFactory).Retry(retryCount).ToTask(); 

我用這個代碼測試這 - 但它確實使這項任務變得非常簡單。你可以通過NuGet「Rx-Main」來獲取這些位。

+0

我在很多其他項目中都使用Rx,這正是我爲什麼想知道e是一種在TPL中執行簡單實施的方法,僅用於重試例程。 –

+0

@JonathanSheely - 夠公平的。我喜歡Rx遠遠超過TPL。 – Enigmativity