有时,我需要在放弃之前将一个操作重试几次。我的代码是:
int retries = 3;
while(true) {
try {
DoSomething();
break; // success!
} catch {
if(--retries == 0) throw;
else Thread.Sleep(1000);
}
}
我想在一个通用的重试函数中重写这个:
TryThreeTimes(DoSomething);
这在c#中可行吗?TryThreeTimes()方法的代码是什么?
以最新的方式实现了LBushkin的答案:
public static async Task Do(Func<Task> task, TimeSpan retryInterval, int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
await Task.Delay(retryInterval);
}
await task();
return;
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
public static async Task<T> Do<T>(Func<Task<T>> task, TimeSpan retryInterval, int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
await Task.Delay(retryInterval);
}
return await task();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
要使用它:
await Retry.Do([TaskFunction], retryInterval, retryAttempts);
而函数[TaskFunction]可以是Task<T>,也可以只是Task。
我要实现这个:
public static bool Retry(int maxRetries, Func<bool, bool> method)
{
while (maxRetries > 0)
{
if (method(maxRetries == 1))
{
return true;
}
maxRetries--;
}
return false;
}
我不会像在其他例子中那样使用异常。在我看来,如果我们期待一种方法不会成功的可能性,那么它的失败也不是例外。我调用的方法如果成功就返回true,失败就返回false。
为什么它是Func<bool>而不是Func<bool>?因此,如果我希望一个方法能够在失败时抛出异常,我有一种方法可以通知它这是最后一次尝试。
因此,我可能会将它用于如下代码:
Retry(5, delegate(bool lastIteration)
{
// do stuff
if (!succeeded && lastIteration)
{
throw new InvalidOperationException(...)
}
return succeeded;
});
or
if (!Retry(5, delegate(bool lastIteration)
{
// do stuff
return succeeded;
}))
{
Console.WriteLine("Well, that didn't work.");
}
如果传递一个方法不使用的参数被证明很尴尬,那么实现一个重载的Retry也很简单,它只接受Func<bool>。
我需要一个支持取消的方法,在此过程中,我添加了返回中间故障的支持。
public static class ThreadUtils
{
public static RetryResult Retry(
Action target,
CancellationToken cancellationToken,
int timeout = 5000,
int retries = 0)
{
CheckRetryParameters(timeout, retries)
var failures = new List<Exception>();
while(!cancellationToken.IsCancellationRequested)
{
try
{
target();
return new RetryResult(failures);
}
catch (Exception ex)
{
failures.Add(ex);
}
if (retries > 0)
{
retries--;
if (retries == 0)
{
throw new AggregateException(
"Retry limit reached, see InnerExceptions for details.",
failures);
}
}
if (cancellationToken.WaitHandle.WaitOne(timeout))
{
break;
}
}
failures.Add(new OperationCancelledException(
"The Retry Operation was cancelled."));
throw new AggregateException("Retry was cancelled.", failures);
}
private static void CheckRetryParameters(int timeout, int retries)
{
if (timeout < 1)
{
throw new ArgumentOutOfRangeException(...
}
if (retries < 0)
{
throw new ArgumentOutOfRangeException(...
}
}
public class RetryResult : IEnumerable<Exception>
{
private readonly IEnumerable<Exception> failureExceptions;
private readonly int failureCount;
protected internal RetryResult(
ICollection<Exception> failureExceptions)
{
this.failureExceptions = failureExceptions;
this.failureCount = failureExceptions.Count;
}
}
public int FailureCount
{
get { return this.failureCount; }
}
public IEnumerator<Exception> GetEnumerator()
{
return this.failureExceptions.GetEnumerator();
}
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
您可以像这样使用重试功能,重试3次,延迟10秒,但不会取消。
try
{
var result = ThreadUtils.Retry(
SomeAction,
CancellationToken.None,
10000,
3);
// it worked
result.FailureCount // but failed this many times first.
}
catch (AggregationException ex)
{
// oops, 3 retries wasn't enough.
}
或者,除非取消,否则每隔5秒永远重试一次。
try
{
var result = ThreadUtils.Retry(
SomeAction,
someTokenSource.Token);
// it worked
result.FailureCount // but failed this many times first.
}
catch (AggregationException ex)
{
// operation was cancelled before success.
}
正如您可以猜到的那样,在我的源代码中,我重载了Retry函数,以支持我希望使用的不同委托类型。