更新5/2017
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); }原版的
编码Retry函数的方法有很多种:您可以使用递归或任务迭代。一段时间以来,希腊.NET用户组中进行了讨论,讨论了执行此操作的不同方法。
如果使用的是F#,则还可以使用异步构造。不幸的是,您至少不能在异步CTP中使用async /
await构造,因为编译器生成的代码不喜欢多次等待或在catch块中重新抛出。
递归版本可能是在C#中构建Retry的最简单方法。以下版本不使用Unwrap,而是在重试之前添加了可选的延迟:
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不幸的是,不可能从Async CTP使用async /
await在C#中写类似的东西,因为编译器不喜欢catch块中的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? YN"); 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; }通过所有后续操作,您可以了解为什么非常需要异步版本的Retry。
更新:
在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); }


