我的應用程序中的所有服務調用均作爲任務實現。當有任務出現故障時,我需要向用戶提供一個對話框以重試最後的操作失敗了。如果用戶選擇重試,程序應該重試任務,否則在記錄異常之後程序的執行應該繼續。任何人都已經對如何實現這個功能有了高層次的想法?任務中出現異常時,根據用戶輸入多次重試任務
回答
UPDATE二千零十七分之五
C#6異常篩選使catch
條款簡單了很多:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
while (true)
{
try
{
var result = await Task.Run(func);
return result;
}
catch when (retryCount-- > 0){}
}
}
和遞歸版本:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
try
{
var result = await Task.Run(func);
return result;
}
catch when (retryCount-- > 0){}
return await Retry(func, retryCount);
}
ORIGINAL
有很多方法來編寫一個重試功能:您可以使用遞歸或任務迭代。在希臘.NET用戶組中有一個discussion,而回到完成這個的不同方式。
如果你使用F#,你也可以使用Async結構。不幸的是,至少在Async CTP中不能使用異步/等待結構,因爲編譯器生成的代碼不像多個等待或catch塊中可能的重新拋出。
遞歸版本可能是在C#中構建重試的最簡單方法。下面的版本不使用展開和重試前增加了一個可選的延遲:
private static Task<T> Retry<T>(Func<T> func, int retryCount, int delay, TaskCompletionSource<T> tcs = null)
{
if (tcs == null)
tcs = new TaskCompletionSource<T>();
Task.Factory.StartNew(func).ContinueWith(_original =>
{
if (_original.IsFaulted)
{
if (retryCount == 0)
tcs.SetException(_original.Exception.InnerExceptions);
else
Task.Factory.StartNewDelayed(delay).ContinueWith(t =>
{
Retry(func, retryCount - 1, delay,tcs);
});
}
else
tcs.SetResult(_original.Result);
});
return tcs.Task;
}
的StartNewDelayed功能來自ParallelExtensionsExtras樣品和使用定時器發生超時時觸發TaskCompletionSource。
的F#版本是簡單了很多:
let retry (asyncComputation : Async<'T>) (retryCount : int) : Async<'T> =
let rec retry' retryCount =
async {
try
let! result = asyncComputation
return result
with exn ->
if retryCount = 0 then
return raise exn
else
return! retry' (retryCount - 1)
}
retry' retryCount
抱歉,系統則無法使用異步寫在C#中類似的東西從異步CTP /等待,因爲編譯器不喜歡裏面等待語句一個catch塊。下面的嘗試也失敗silenty,因爲運行時不喜歡異常後遇到的await:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
while (true)
{
try
{
var result = await TaskEx.Run(func);
return result;
}
catch
{
if (retryCount == 0)
throw;
retryCount--;
}
}
}
至於詢問用戶,你可以修改重試打電話,詢問用戶,並返回任務的功能通過TaskCompletionSource觸發下一步,當用戶的答案,如:
private static Task<bool> AskUser()
{
var tcs = new TaskCompletionSource<bool>();
Task.Factory.StartNew(() =>
{
Console.WriteLine(@"Error Occured, continue? Y\N");
var response = Console.ReadKey();
tcs.SetResult(response.KeyChar=='y');
});
return tcs.Task;
}
private static Task<T> RetryAsk<T>(Func<T> func, int retryCount, TaskCompletionSource<T> tcs = null)
{
if (tcs == null)
tcs = new TaskCompletionSource<T>();
Task.Factory.StartNew(func).ContinueWith(_original =>
{
if (_original.IsFaulted)
{
if (retryCount == 0)
tcs.SetException(_original.Exception.InnerExceptions);
else
AskUser().ContinueWith(t =>
{
if (t.Result)
RetryAsk(func, retryCount - 1, tcs);
});
}
else
tcs.SetResult(_original.Result);
});
return tcs.Task;
}
與所有的延續,你可以看到爲什麼重試的異步版本是這麼希望的。
UPDATE:
在Visual Studio 2012 Beta版以下兩個版本的工作:
while循環一個版本:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
while (true)
{
try
{
var result = await Task.Run(func);
return result;
}
catch
{
if (retryCount == 0)
throw;
retryCount--;
}
}
}
和遞歸版本:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
try
{
var result = await Task.Run(func);
return result;
}
catch
{
if (retryCount == 0)
throw;
}
return await Retry(func, --retryCount);
}
感謝您的詳細解釋,將嘗試這一點,讓你知道 –
+1好的工作。 [在下面添加了一個段落](http://stackoverflow.com/a/16354355/11635) –
當處於高層次時,我發現它有助於根據您擁有的和想要的來製作函數簽名。
您有:
- ,給你一個任務(
Func<Task>
)的功能。我們將使用該函數,因爲任務本身通常不是可重試的。 - ,決定是否整體任務完成或應該重試(
Func<Task, bool>
)
函數你想:
- 總任務
那麼你就會有一個功能如:
Task Retry(Func<Task> action, Func<Task, bool> shouldRetry);
擴展功能內部的做法,任務幾乎有2個操作來處理它們,閱讀它們的狀態和ContinueWith
。爲了完成你自己的任務,TaskCompletionSource
是一個很好的起點。第一次嘗試可能看起來像:
//error checking
var result = new TaskCompletionSource<object>();
action().ContinueWith((t) =>
{
if (shouldRetry(t))
action();
else
{
if (t.IsFaulted)
result.TrySetException(t.Exception);
//and similar for Canceled and RunToCompletion
}
});
這裏的明顯的問題是,只有1重試將不會發生。爲了解決這個問題,你需要爲函數自己調用一個方法。通常的方式與lambda表達式來做到這一點是這樣的:
//error checking
var result = new TaskCompletionSource<object>();
Func<Task, Task> retryRec = null; //declare, then assign
retryRec = (t) => { if (shouldRetry(t))
return action().ContinueWith(retryRec).Unwrap();
else
{
if (t.IsFaulted)
result.TrySetException(t.Exception);
//and so on
return result.Task; //need to return something
}
};
action().ContinueWith(retryRec);
return result.Task;
感謝,將試着讓你知道 –
這裏是一個riffed版本Panagiotis Kanavos's excellent answer我已經測試過,並在生產中使用。
它解決一些事情,是很重要的對我說:
- 希望能夠決定基於目前從前面的嘗試和努力例外數量是否重試
- 不想依靠
async
(較少環境約束) - 希望有在出現故障的情況下,所得
Exception
包括從每次嘗試細節
static Task<T> RetryWhile<T>(
Func<int, Task<T>> func,
Func<Exception, int, bool> shouldRetry)
{
return RetryWhile<T>(func, shouldRetry, new TaskCompletionSource<T>(), 0, Enumerable.Empty<Exception>());
}
static Task<T> RetryWhile<T>(
Func<int, Task<T>> func,
Func<Exception, int, bool> shouldRetry,
TaskCompletionSource<T> tcs,
int previousAttempts, IEnumerable<Exception> previousExceptions)
{
func(previousAttempts).ContinueWith(antecedent =>
{
if (antecedent.IsFaulted)
{
var antecedentException = antecedent.Exception;
var allSoFar = previousExceptions
.Concat(antecedentException.Flatten().InnerExceptions);
if (shouldRetry(antecedentException, previousAttempts))
RetryWhile(func,shouldRetry,previousAttempts+1, tcs, allSoFar);
else
tcs.SetException(allLoggedExceptions);
}
else
tcs.SetResult(antecedent.Result);
}, TaskContinuationOptions.ExecuteSynchronously);
return tcs.Task;
}
- 1. 嘗試創建新的任務調度程序任務時出現異常
- 2. 根據輸入調用另一個任務的ANT任務
- 3. 任務異常
- 4. 構建即時應用任務時出現NullPointer異常
- 5. 根據用戶輸入使用django-celery安排任務
- 6. JavaFX任務,服務 - 異常
- 7. Visual Studio 2013崩潰時出現異常 - 任務調度程序拋出異常
- 8. Java執行任務時執行了多次重試和超時
- 9. 異步任務異常Android
- 10. 運行TFS 2015自定義構建任務時出現異常
- 11. 面料:管任務的另一個任務的輸入輸出
- 12. C#TPL任務傳播異常 - 多級任務
- 13. SBT - 根據任務
- 14. Android異步任務重用
- 15. 任務輸入與任務源
- 16. 做任務讓用戶知道任務完成時的多個任務
- 17. 如何多次執行異步任務?
- 18. 根據用戶輸入重複次數
- 19. 需要用戶輸入的ant任務?
- 20. 計劃任務不會拋出異常
- 21. 任務吞下拋出的異常
- 22. 任務計劃拋出異常
- 23. 異步任務vs任務
- 24. Java多線程 - 每次任務完成任務時調度任務
- 25. 用戶輸入到Url使用異步任務
- 26. 計劃任務出現了兩次
- 27. 任務和異常沉默
- 28. 按任務投擲異常
- 29. 異常失敗的任務
- 30. 服務器如何根據客戶端輸入執行不同的任務?
@svick我沒試過來實現這個功能它是一個未來的任務來了,看起來我們得到了有趣的想法已經,已經通過他們去詳細,並給它一個嘗試 –