Vas*_*nan 13 c# wpf exception-handling task task-parallel-library
在我的应用程序中的所有服务调用实现为tasks.When永远的任务发生故障,我需要一个对话框,向用户呈现failed.If用户选择重试程序应该重试任务重试最后一次操作,否则记录异常后,程序的执行应该继续.任何人都对如何实现这个功能有了很高的想法?
Pan*_*vos 36
更新时间2017年5月
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){}
}
}
Run Code Online (Sandbox Code Playgroud)
和递归版本:
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);
}
Run Code Online (Sandbox Code Playgroud)
原版的
编写重试函数的方法有很多种:您可以使用递归或任务迭代.有一个讨论希腊.NET用户组中的不同的方式做的正是这种而回.
如果您使用F#,您也可以使用Async构造.不幸的是,至少在Async CTP中你不能使用async/await结构,因为编译器生成的代码不喜欢多个等待或catch块中可能的重新抛出.
递归版本可能是在C#中构建重试的最简单方法.以下版本不使用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;
}
Run Code Online (Sandbox Code Playgroud)
所述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
Run Code Online (Sandbox Code Playgroud)
不幸的是,不可能在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--;
}
}
}
Run Code Online (Sandbox Code Playgroud)
至于询问用户,您可以修改重试以调用询问用户的函数,并通过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;
}
Run Code Online (Sandbox Code Playgroud)
通过所有延续,您可以了解为什么异步版本的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--;
}
}
}
Run Code Online (Sandbox Code Playgroud)
和递归版本:
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);
}
Run Code Online (Sandbox Code Playgroud)
这是Panagiotis Kanavos 出色答案的重复版本,我已经测试过并在生产中使用。
它解决了一些对我很重要的事情:
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;
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
10028 次 |
最近记录: |