我的应用程序中的所有服务调用均作为任务实现。当有任务出现故障时,我需要向用户提供一个对话框以重试最后的操作失败了。如果用户选择重试,程序应该重试任务,否则在记录异常之后程序的执行应该继续。任何人都已经对如何实现这个功能有了高层次的想法?任务中出现异常时,根据用户输入多次重试任务
回答
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我没试过来实现这个功能它是一个未来的任务来了,看起来我们得到了有趣的想法已经,已经通过他们去详细,并给它一个尝试 –