问题描述
在.NET/C#的程序开发中,有时候需要对一些失败的任务进行多次的重试,如果重试的次数达到我们设定的阀值,则再放弃任务,比如有以下的C#伪代码:
int retries = 3;
while(true) {
try {
DoSomething();
break; // success!
} catch {
if(--retries == 0) throw;
else Thread.Sleep(1000);
}
}
现在,需要使用C#将上述的代码块进行重写成如下的格式:
TryThreeTimes(DoSomething);
如何使用C#对其进行重写,如果可以,又如何实现呢?
方案一
使用一个静态类和静态的泛型方法来处理,创建通用的任务重试机制,我们可以使用Action
作为参数,如下的Retry
静态类及对应的方法:
public static class Retry
{
public static void Do(
Action action,
TimeSpan retryInterval,
int maxAttemptCount = 3)
{
Do<object>(() =>
{
action();
return null;
}, retryInterval, maxAttemptCount);
}
public static T Do<T>(
Func<T> action,
TimeSpan retryInterval,
int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
Thread.Sleep(retryInterval);
}
return action();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
}
调用方法如下:
Retry.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));
或者:
Retry.Do(SomeFunctionThatCanFail, TimeSpan.FromSeconds(1));
或者:
int result = Retry.Do(SomeFunctionWhichReturnsInt, TimeSpan.FromSeconds(1), 4);
甚至,你也可以自己重载一个async
的异步方法。
方案二
使用第三组件,如Polly
,Polly
是一个.NET Framework下的任务重试解决方案组件,用Polly
你可以非常方便地完成如:Retry
,Retry Forever
,Wait and Retry
或者Circuit Breaker
等等重试操作,并且Polly
的重试写法是链式的,如:
Policy
.Handle<SqlException>(ex => ex.Number == 1205)
.Or<ArgumentException>(ex => ex.ParamName == "example")
.WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(3))
.Execute(() => DoSomething());
更多文档请见 Polly官方地址
方案三
任务重试机制的简单方法:
public void TryThreeTimes(Action action)
{
var tries = 3;
while (true) {
try {
action();
break; // success!
} catch {
if (--tries == 0)
throw;
Thread.Sleep(1000);
}
}
}
调用方法:
TryThreeTimes(DoSomething);
或者
TryThreeTimes(() => DoSomethingElse(withLocalVariable));
需要更多参数,如重试间隔、重试次数等:
public void DoWithRetry(Action action, TimeSpan sleepPeriod, int tryCount = 3)
{
if (tryCount <= 0)
throw new ArgumentOutOfRangeException(nameof(tryCount));
while (true) {
try {
action();
break; // success!
} catch {
if (--tryCount == 0)
throw;
Thread.Sleep(sleepPeriod);
}
}
}
调用方法:
DoWithRetry(DoSomething, TimeSpan.FromSeconds(2), tryCount: 10);
或者我们可以使用async
异步方法,如:
public async Task DoWithRetryAsync(Func<Task> action, TimeSpan sleepPeriod, int tryCount = 3)
{
if (tryCount <= 0)
throw new ArgumentOutOfRangeException(nameof(tryCount));
while (true) {
try {
await action();
return; // success!
} catch {
if (--tryCount == 0)
throw;
await Task.Delay(sleepPeriod);
}
}
}
调用方法:
await DoWithRetryAsync(DoSomethingAsync, TimeSpan.FromSeconds(2), tryCount: 10);
方案四
以下方案可以执行重试方法以及重试失败的异常处理方法:
public static T RetryMethod<T>(Func<T> method, int numRetries, int retryTimeout, Action onFailureAction)
{
Guard.IsNotNull(method, "method");
T retval = default(T);
do
{
try
{
retval = method();
return retval;
}
catch
{
onFailureAction();
if (numRetries <= 0) throw;
Thread.Sleep(retryTimeout);
}
} while (numRetries-- > 0);
return retval;
}
方案五
public static void RetryForExcpetionType(Action action, Type retryOnExceptionType, int numRetries, int retryTimeout)
{
if (action == null)
throw new ArgumentNullException("action");
if (retryOnExceptionType == null)
throw new ArgumentNullException("retryOnExceptionType");
while (true)
{
try
{
action();
return;
}
catch(Exception e)
{
if (--numRetries <= 0 || !retryOnExceptionType.IsAssignableFrom(e.GetType()))
throw;
if (retryTimeout > 0)
System.Threading.Thread.Sleep(retryTimeout);
}
}
}
调用方法:
RetryForExcpetionType(DoSomething, typeof(TimeoutException), 5, 1000);
版权声明:本作品系原创,版权归码友网所有,如未经许可,禁止任何形式转载,违者必究。
发表评论
登录用户才能发表评论, 请 登 录 或者 注册