首页 / .NET / 正文

[C#].NET/C#程序开发中如何更优美地实现失败任务重试的逻辑?

8668 发布于: 2018-02-25 读完约需13分钟

问题描述

在.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的异步方法。

方案二

使用第三组件,如PollyPolly是一个.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);

版权声明:本作品系原创,版权归码友网所有,如未经许可,禁止任何形式转载,违者必究。

上一篇: [.NET]C#/.NET程序开发中如何截断一个字符串?

下一篇: [C#].NET/C#程序开发中将多个字典合并成一个字典的方法有哪些?

本文永久链接码友网 » [C#].NET/C#程序开发中如何更优美地实现失败任务重试的逻辑?

分享扩散:

发表评论

登录用户才能发表评论, 请 登 录 或者 注册