【问题标题】:Cleanest way to write retry logic?编写重试逻辑的最简洁方法?
【发布时间】:2010-12-06 11:58:02
【问题描述】:

有时我需要在放弃之前重试几次操作。我的代码是这样的:

int retries = 3;
while(true) {
  try {
    DoSomething();
    break; // success!
  } catch {
    if(--retries == 0) throw;
    else Thread.Sleep(1000);
  }
}

我想用一个通用的重试函数重写这个,比如:

TryThreeTimes(DoSomething);

在 C# 中可以吗? TryThreeTimes() 方法的代码是什么?

【问题讨论】:

  • 一个简单的循环还不够?为什么不多次迭代并执行逻辑?
  • 就个人而言,我会非常警惕任何此类辅助方法。使用 lambda 实现当然是可能的,但模式本身非常臭,因此为它引入一个帮助器(这意味着它经常重复)本身就非常可疑,并且强烈暗示了糟糕的整体设计。
  • 在我的例子中,我的 DoSomething() 正在远程机器上做一些事情,比如删除文件,或者尝试访问网络端口。在这两种情况下,DoSomething 何时会成功都存在重大的时间问题,并且由于地处偏远,我无法监听任何事件。所以,是的,它很臭。欢迎提出建议。
  • @PavelMinaev 为什么使用重试会暗示糟糕的整体设计?如果您编写了很多连接集成点的代码,那么使用重试绝对是您应该认真考虑使用的一种模式。

标签: c# .net


【解决方案1】:

如果用作一般异常处理机制,那么简单地重试相同调用的一揽子 catch 语句可能会很危险。话虽如此,这是一个基于 lambda 的重试包装器,您可以将其与任何方法一起使用。我选择将重试次数和重试超时作为参数考虑在内,以获得更大的灵活性:

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 重载。

【讨论】:

  • +1,特别是用于警告和错误检查。不过,如果这将异常的类型作为泛型参数(其中 T: Exception)传递给我,我会更舒服。
  • 我的意图是“重试”实际上意味着重试。但是将其更改为“尝试”并不难。只要名称保持有意义。还有其他改进代码的机会,例如检查负重试或负超时。我省略了这些主要是为了保持示例简单......但在实践中,这些可能是对实现的很好的增强。
  • 我们在大容量 Biztalk 应用程序中使用类似的模式来访问我们的数据库,但有两个改进:我们为不应重试的异常设置了黑名单,我们存储了第一个发生的异常并抛出当重试最终失败时。原因是第二个和后面的异常通常与第一个不同。在这种情况下,您在仅重新抛出最后一个异常时隐藏了最初的问题。
  • @Dexters 我们抛出一个新异常,原始异常作为内部异常。原始堆栈跟踪可用作内部异常的属性。
  • 您也可以尝试使用诸如Polly 之类的开源库来处理此问题。在重试之间等待的灵活性要大得多,并且已被许多使用该项目的其他人验证过。示例:Policy.Handle&lt;DivideByZeroException&gt;().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
【解决方案2】:

你应该试试Polly。它是我编写的一个.NET库,允许开发人员以流畅的方式表达Retry、Retry Forever、Wait and Retry或Circuit Breaker等瞬态异常处理策略。

示例

Policy
    .Handle<SqlException>(ex => ex.Number == 1205)
    .Or<ArgumentException>(ex => ex.ParamName == "example")
    .WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(3))
    .Execute(() => DoSomething());

【讨论】:

  • 什么是 OnRetry 委托?我认为这是发生异常时我们需要执行的操作。因此,当发生异常时,OnRetry 委托将调用,然后执行委托。是这样吗?
  • 我应该在哪里使用这个 sn-p 代码?如果答案是startup.cs,如何注册Policy?
  • 问:什么是 OnRetry 委托? A:它只允许您在执行重试时执行某些操作(例如,记录某些内容)。你不需要在那里调用 Execute,它会自动发生。
【解决方案3】:
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/await:

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);

【讨论】:

  • 最好将 if 更改为:--retryCount &lt;= 0,因为如果您想通过将其设置为 0 来禁用重试,这将永远持续下去。从技术上讲,术语 retryCount 并不是一个好名字,因为如果将其设置为 1,它将不会重试。要么将其重命名为 tryCount,要么将 -- 放在后面。
  • @saille 我同意。然而,OP(和所有其他答案)正在使用Thread.Sleep。替代方法是使用计时器,或者现在更可能使用async 重试Task.Delay
  • 我添加了一个异步版本。
  • 仅在操作returns truebreakFunc&lt;bool&gt;
  • @ibda 如果你想释放线程去做其他工作,而不是睡觉,你只会使用异步版本。例如,如果您在线程池上运行工作,则不应让这些线程进入睡眠状态。
【解决方案4】:

这可能是个坏主意。首先,它象征着格言“精神错乱的定义是两次做同样的事情,每次都期待不同的结果”。其次,这种编码模式不能很好地与自身组合。例如:

假设您的网络硬件层在失败时重新发送一个数据包 3 次,例如在失败之间等待一秒钟。

现在假设软件层在数据包失败时重新发送失败通知 3 次。

现在假设通知层在通知传递失败时重新激活通知 3 次。

现在假设错误报告层在通知失败时重新激活通知层 3 次。

现在假设 Web 服务器在错误失败时重新激活错误报告 3 次。

现在假设 Web 客户端在收到服务器错误时重新发送请求 3 次。

现在假设网络交换机上应该将通知路由给管理员的线路已拔下。 Web 客户端的用户何时最终收到他们的错误消息?大约十二分钟后我就成功了。

以免您认为这只是一个愚蠢的示例:我们已经在客户代码中看到了这个错误,尽管比我在这里描述的要糟糕得多。在特定的客户代码中,发生错误情况与最终报告给用户之间的间隔是几个 ,因为有很多层会自动重试并等待。试想一下,如果重试 10 次而不是 3 次会发生什么。

通常正确处理错误情况的方法是立即报告并让用户决定要做什么。如果用户想要创建自动重试策略,让他们创建该策略在软件抽象的适当级别。

【讨论】:

  • +1。 Raymond 在这里分享了一个真实的例子,blogs.msdn.com/oldnewthing/archive/2005/11/07/489807.aspx
  • -1 此建议对于自动化批处理系统遇到的瞬时网络故障无用。
  • 不确定这是不是在说“不要这样做”,然后是“这样做”。大多数问这个问题的人可能是从事软件抽象工作的人。
  • 当您长时间运行使用网络资源(例如 Web 服务)的批处理作业时,您不能期望网络是 100% 可靠的。在您使用它时,偶尔会出现超时、套接字断开连接,甚至可能出现虚假的路由故障或服务器中断。一种选择是失败,但这可能意味着稍后重新开始一项冗长的工作。另一种选择是在适当的延迟下重试几次,看看它是否是暂时的问题,然后失败。我同意你必须注意的构图。但有时它是最好的选择。
  • 我认为您在答案开头使用的报价很有趣。如果以前的经验经常给你相同的结果,“期待不同的结果”只是一种精神错乱。虽然软件建立在对一致性的承诺之上,但在某些情况下,我们肯定需要与我们无法控制的不可靠力量进行交互。
【解决方案5】:

Transient Fault Handling Application Block 提供了可扩展的重试策略集合,包括:

  • 增量
  • 固定间隔
  • 指数回退

它还包括一系列基于云的服务的错误检测策略。

有关详细信息,请参阅开发人员指南的this chapter

可通过NuGet 获得(搜索“topaz”)。

【讨论】:

  • 有趣。您可以在 Windows Azure 之外使用它吗,比如在 Winforms 应用程序中?
  • 当然。使用核心重试机制并提供自己的检测策略。我们有意将它们解耦。在此处找到核心 nuget 包:nuget.org/packages/TransientFaultHandling.Core
  • 此外,该项目现在在 Apache 2.0 下并接受社区贡献。 aka.ms/entlibopen
  • @Alex。它的各个部分正在进入平台。
  • 现在已弃用,上次我使用它时,它包含一些据我所知尚未修复且永远不会修复的错误:github.com/MicrosoftArchive/…
【解决方案6】:

我是递归和扩展方法的粉丝,所以这是我的两分钱:

public static void InvokeWithRetries(this Action @this, ushort numberOfRetries)
{
    try
    {
        @this();
    }
    catch
    {
        if (numberOfRetries == 0)
            throw;

        InvokeWithRetries(@this, --numberOfRetries);
    }
}

【讨论】:

    【解决方案7】:

    允许函数和重试消息

    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; // improved to avoid silent failure
          Thread.Sleep(retryTimeout);
       }
    } while (numRetries-- > 0);
      return retval;
    }
    

    【讨论】:

    【解决方案8】:

    您也可以考虑添加要重试的异常类型。例如,这是您要重试的超时异常吗?数据库异常?

    RetryForExcpetionType(DoSomething, typeof(TimeoutException), 5, 1000);
    
    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);
            }
        }
    }
    

    您可能还注意到,所有其他示例在测试重试次数 == 0 时都存在类似问题,并且在给定负值时重试无穷大或无法引发异常。 Sleep(-1000) 也会在上面的 catch 块中失败。取决于您期望人们有多“愚蠢”,但防御性编程永远不会受到伤害。

    【讨论】:

    • +1,但是为什么不做 RetryForException(...) where T: Exception,然后 catch(T e)?刚刚试了一下,效果很好。
    • 无论是这里还是这里,因为我不需要对 Type 做任何事情,只要我认为一个普通的旧参数就可以了。
    • @TrueWill 显然 catch(T ex) 根据这篇文章stackoverflow.com/questions/1577760/… 有一些错误
    • 更新:实际上我一直在使用的一个更好的实现需要一个 Predicate 委托,如果重试合适,则返回 true。这允许您使用本机错误代码或异常的其他属性来确定重试是否适用。例如 HTTP 503 代码。
    • “同样 Sleep(-1000) 将在上面的 catch 块中失败”......使用 TimeSpan 你不会遇到这个问题。加上 TimeSpan 更加灵活和自我描述。从您的“int retryTimeout”签名中,我怎么知道 retryTimeout 是 MS、秒、分钟、年? ;-)
    【解决方案9】:

    使用 C# 6.0 保持简单

    public async Task<T> Retry<T>(Func<T> action, TimeSpan retryInterval, int retryCount)
    {
        try
        {
            return action();
        }
        catch when (retryCount != 0)
        {
            await Task.Delay(retryInterval);
            return await Retry(action, retryInterval, --retryCount);
        }
    }
    

    【讨论】:

    • 我有点好奇,这会因为返回相同的可等待方法而产生大量重试次数和间隔高的线程吗?
    【解决方案10】:

    使用波莉

    https://github.com/App-vNext/Polly-Samples

    这是我与 Polly 一起使用的重试泛型

    public T Retry<T>(Func<T> action, int retryCount = 0)
    {
        PolicyResult<T> policyResult = Policy
         .Handle<Exception>()
         .Retry(retryCount)
         .ExecuteAndCapture<T>(action);
    
        if (policyResult.Outcome == OutcomeType.Failure)
        {
            throw policyResult.FinalException;
        }
    
        return policyResult.Result;
    }
    

    这样使用

    var result = Retry(() => MyFunction()), 3);
    

    【讨论】:

      【解决方案11】:

      在之前的工作基础上,我想到了从三个方面来增强重试逻辑:

      1. 指定要捕获/重试的异常类型。这是主要的增强功能,因为重试任何异常都是完全错误的。
      2. 不将最后一次尝试嵌套在 try/catch 中,从而获得更好的性能
      3. 使其成为Action 扩展方法

        static class ActionExtensions
        {
          public static void InvokeAndRetryOnException<T> (this Action action, int retries, TimeSpan retryDelay) where T : Exception
          {
            if (action == null)
              throw new ArgumentNullException("action");
        
            while( retries-- > 0 )
            {
              try
              {
                action( );
                return;
              }
              catch (T)
              {
                Thread.Sleep( retryDelay );
              }
            }
        
            action( );
          }
        }
        

      然后可以像这样调用该方法(当然也可以使用匿名方法):

      new Action( AMethodThatMightThrowIntermittentException )
        .InvokeAndRetryOnException<IntermittentException>( 2, TimeSpan.FromSeconds( 1 ) );
      

      【讨论】:

      • 这太棒了。但就我个人而言,我不会称它为“retryTimeout”,因为它并不是真正的超时。也许是“重试延迟”?
      【解决方案12】:

      以最新方式实现了 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&lt;T&gt; 或只是Task

      【讨论】:

      • 谢谢你,费边!这应该一直被投票到顶部!
      • @MarkLauter 简短的回答是肯定的。 ;-)
      【解决方案13】:

      我会实现这个:

      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&lt;bool, bool&gt; 而不仅仅是Func&lt;bool&gt;?因此,如果我想要一个方法能够在失败时抛出异常,我有办法通知它这是最后一次尝试。

      所以我可能会将它与以下代码一起使用:

      Retry(5, delegate(bool lastIteration)
         {
             // do stuff
             if (!succeeded && lastIteration)
             {
                throw new InvalidOperationException(...)
             }
             return succeeded;
         });
      

      if (!Retry(5, delegate(bool lastIteration)
         {
             // do stuff
             return succeeded;
         }))
      {
         Console.WriteLine("Well, that didn't work.");
      }
      

      如果传递一个方法不使用的参数被证明很尴尬,那么实现Retry 的重载也很简单,它也只需要一个Func&lt;bool&gt;

      【讨论】:

      • +1 用于避免异常。虽然我会做一个 void Retry(...) 并抛出一些东西?布尔返回和/或返回代码经常被忽视。
      • “如果我们期望一个方法不会成功,那么它的失败不是异常”——虽然在某些情况下确实如此,但异常不一定意味着异常。它用于错误处理。不能保证调用者会检查布尔结果。 保证将处理异常(如果没有其他操作,运行时将关闭应用程序)。
      • 我找不到参考,但我相信 .NET 将异常定义为“一个方法没有按照它说的去做”。 1 目的是使用异常来指示问题,而不是 Win32 模式要求调用者检查函数是否成功的返回值。
      • 但异常不仅仅“表明存在问题”。它们还包括大量诊断信息,这些信息需要花费时间和内存来编译。显然,在某些情况下,这一点都不重要。但是它有很多地方。 .NET 不使用控制流异常(例如,与 Python 使用 StopIteration 异常相比),这是有原因的。
      • TryDo 方法模式是一个滑坡。在不知不觉中,您的整个调用堆栈将由TryDo 方法组成。为了避免这种混乱,发明了例外。
      【解决方案14】:

      6 年后更新:现在我认为下面的方法很糟糕。要创建重试逻辑,我们应该考虑使用 Polly 之类的库。


      我的async重试方法的实现:

      public static async Task<T> DoAsync<T>(Func<dynamic> action, TimeSpan retryInterval, int retryCount = 3)
          {
              var exceptions = new List<Exception>();
      
              for (int retry = 0; retry < retryCount; retry++)
              {
                  try
                  {
                      return await action().ConfigureAwait(false);
                  }
                  catch (Exception ex)
                  {
                      exceptions.Add(ex);
                  }
      
                  await Task.Delay(retryInterval).ConfigureAwait(false);
              }
              throw new AggregateException(exceptions);
          }
      

      重点:我用.ConfigureAwait(false);Func&lt;dynamic&gt;代替Func&lt;T&gt;

      【讨论】:

      • 这没有提供问题的答案。请考虑将您的答案作为一个新问题发布,使用页面顶部的“”按钮,然后发布您自己对该问题的答案,以便与社区分享您学到的知识。
      • C# 5.0 比 codereview.stackexchange.com/q/55983/54000 简单得多,但可能应该注入 CansellactionToken。
      • 这个实现有问题。在最后一次重试之后,就在放弃之前,Task.Delay 被无缘无故地调用。
      • @HappyNomad 这是一个已有 6 年历史的答案,现在我认为这是创建重试逻辑的一种非常糟糕的方法 :)) 感谢您的通知。我会根据这个考虑更新我的答案。
      【解决方案15】:

      我需要一个支持取消的方法,当我这样做时,我添加了对返回中间故障的支持。

      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();
          }
      }
      

      你可以像这样使用Retry函数,重试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.
      }
      

      或者,除非取消,否则每五秒永远重试一次。

      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 函数以支持我希望使用的不同的委托类型。

      【讨论】:

        【解决方案16】:

        此方法允许重试某些异常类型(立即抛出其他异常)。

        public static void DoRetry(
            List<Type> retryOnExceptionTypes,
            Action actionToTry,
            int retryCount = 5,
            int msWaitBeforeEachRety = 300)
        {
            for (var i = 0; i < retryCount; ++i)
            {
                try
                {
                    actionToTry();
                    break;
                }
                catch (Exception ex)
                {
                    // Retries exceeded
                    // Throws on last iteration of loop
                    if (i == retryCount - 1) throw;
        
                    // Is type retryable?
                    var exceptionType = ex.GetType();
                    if (!retryOnExceptionTypes.Contains(exceptionType))
                    {
                        throw;
                    }
        
                    // Wait before retry
                    Thread.Sleep(msWaitBeforeEachRety);
                }
            }
        }
        public static void DoRetry(
            Type retryOnExceptionType,
            Action actionToTry,
            int retryCount = 5,
            int msWaitBeforeEachRety = 300)
                => DoRetry(new List<Type> {retryOnExceptionType}, actionToTry, retryCount, msWaitBeforeEachRety);
        

        示例用法:

        DoRetry(typeof(IOException), () => {
            using (var fs = new FileStream(requestedFilePath, FileMode.Create, FileAccess.Write))
            {
                fs.Write(entryBytes, 0, entryBytes.Length);
            }
        });
        

        【讨论】:

          【解决方案17】:

          Exponential backoff 是一个很好的重试策略,而不是简单地尝试 x 次。您可以使用 Polly 之类的库来实现它。

          【讨论】:

          • Polly 示例使用它?
          【解决方案18】:

          对于那些想要重试任何异常或显式设置异常类型的选项,请使用:

          public class RetryManager 
          {
              public void Do(Action action, 
                              TimeSpan interval, 
                              int retries = 3)
              {
                  Try<object, Exception>(() => {
                      action();
                      return null;
                  }, interval, retries);
              }
          
              public T Do<T>(Func<T> action, 
                              TimeSpan interval, 
                              int retries = 3)
              {
                  return Try<T, Exception>(
                        action
                      , interval
                      , retries);
              }
          
              public T Do<E, T>(Func<T> action, 
                                 TimeSpan interval, 
                                 int retries = 3) where E : Exception
              {
                  return Try<T, E>(
                        action
                      , interval
                      , retries);
              }
          
              public void Do<E>(Action action, 
                                 TimeSpan interval, 
                                 int retries = 3) where E : Exception
              {
                  Try<object, E>(() => {
                      action();
                      return null;
                  }, interval, retries);
              }
          
              private T Try<T, E>(Func<T> action, 
                                 TimeSpan interval, 
                                 int retries = 3) where E : Exception
              {
                  var exceptions = new List<E>();
          
                  for (int retry = 0; retry < retries; retry++)
                  {
                      try
                      {
                          if (retry > 0)
                              Thread.Sleep(interval);
                          return action();
                      }
                      catch (E ex)
                      {
                          exceptions.Add(ex);
                      }
                  }
          
                  throw new AggregateException(exceptions);
              }
          }
          

          【讨论】:

            【解决方案19】:

            我有两个使用 Polly 的模式实现。一种是异步的。

            我的同步方法是基于this answer by Erik Bergstedt

            public static T Retry<T>(Func<T> action, TimeSpan retryWait, int retryCount = 0)
            {
                PolicyResult<T> policyResult = Policy
                    .Handle<ApiException>(ex => ex.ResponseCode == (int)HttpStatusCode.TooManyRequests)
                    .WaitAndRetry(retryCount, retryAttempt => retryWait)
                    .ExecuteAndCapture(action);
            
                if (policyResult.Outcome == OutcomeType.Failure)
                {
                    throw policyResult.FinalException;
                }
            
                return policyResult.Result;
            }
            

            异步:

            public static async Task<T> RetryAsync<T>(Func<Task<T>> action, TimeSpan retryWait, int retryCount = 0)
            {
                PolicyResult<T> policyResult = await Policy
                    .Handle<ApiException>(ex => ex.ResponseCode == (int)HttpStatusCode.TooManyRequests)
                    .WaitAndRetryAsync(retryCount, retryAttempt => retryWait)
                    .ExecuteAndCaptureAsync(action);
            
                if (policyResult.Outcome == OutcomeType.Failure)
                {
                    throw policyResult.FinalException;
                }
            
                return policyResult.Result;
            }
            

            允许传入异常类型以及异常类型的 lambda 也很容易。

            【讨论】:

              【解决方案20】:

              这是一个async/await 版本,它聚合异常并支持取消。

              /// <seealso href="https://docs.microsoft.com/en-us/azure/architecture/patterns/retry"/>
              protected static async Task<T> DoWithRetry<T>( Func<Task<T>> action, CancellationToken cancelToken, int maxRetries = 3 )
              {
                  var exceptions = new List<Exception>();
              
                  for ( int retries = 0; !cancelToken.IsCancellationRequested; retries++ )
                      try {
                          return await action().ConfigureAwait( false );
                      } catch ( Exception ex ) {
                          exceptions.Add( ex );
              
                          if ( retries < maxRetries )
                              await Task.Delay( 500, cancelToken ).ConfigureAwait( false ); //ease up a bit
                          else
                              throw new AggregateException( "Retry limit reached", exceptions );
                      }
              
                  exceptions.Add( new OperationCanceledException( cancelToken ) );
                  throw new AggregateException( "Retry loop was canceled", exceptions );
              }
              

              【讨论】:

                【解决方案21】:

                或者,要不要做的更整洁一些……

                int retries = 3;
                while (retries > 0)
                {
                  if (DoSomething())
                  {
                    retries = 0;
                  }
                  else
                  {
                    retries--;
                  }
                }
                

                我认为通常应该避免将抛出异常作为一种机制,除非您在边界之间传递它们(例如构建其他人可以使用的库)。为什么不让DoSomething() 命令成功返回true,否则返回false

                编辑: 这也可以像其他人建议的那样封装在一个函数中。唯一的问题是,如果您不是自己编写 DoSomething() 函数

                【讨论】:

                • “我认为通常应该避免抛出异常作为一种机制,除非你在边界之间传递它们” - 我完全不同意。你怎么知道调用者检查了你的错误(或更糟糕的是,null)返回?为什么代码失败了? False 什么也没告诉你。如果调用者必须将失败向上传递到堆栈怎么办?阅读msdn.microsoft.com/en-us/library/ms229014.aspx - 这些是用于库的,但它们对内部代码同样有意义。在团队中,其他人可能会调用您的代码。
                【解决方案22】:

                我需要将一些参数传递给我的方法以重试,并获得一个结果值;所以我需要一个表达.. 我建立了这个可以完成工作的课程(它受到了 LBushkin 的启发) 你可以这样使用它:

                static void Main(string[] args)
                {
                    // one shot
                    var res = Retry<string>.Do(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);
                
                    // delayed execute
                    var retry = new Retry<string>(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);
                    var res2 = retry.Execute();
                }
                
                static void fix()
                {
                    Console.WriteLine("oh, no! Fix and retry!!!");
                }
                
                static string retryThis(string tryThis)
                {
                    Console.WriteLine("Let's try!!!");
                    throw new Exception(tryThis);
                }
                
                public class Retry<TResult>
                {
                    Expression<Func<TResult>> _Method;
                    int _NumRetries;
                    TimeSpan _RetryTimeout;
                    Action _OnFailureAction;
                
                    public Retry(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
                    {
                        _Method = method;
                        _NumRetries = numRetries;
                        _OnFailureAction = onFailureAction;
                        _RetryTimeout = retryTimeout;
                    }
                
                    public TResult Execute()
                    {
                        TResult result = default(TResult);
                        while (_NumRetries > 0)
                        {
                            try
                            {
                                result = _Method.Compile()();
                                break;
                            }
                            catch
                            {
                                _OnFailureAction();
                                _NumRetries--;
                                if (_NumRetries <= 0) throw; // improved to avoid silent failure
                                Thread.Sleep(_RetryTimeout);
                            }
                        }
                        return result;
                    }
                
                    public static TResult Do(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
                    {
                        var retry = new Retry<TResult>(method, numRetries, retryTimeout, onFailureAction);
                        return retry.Execute();
                    }
                }
                

                ps。 LBushkin 的解决方案再重试一次 =D

                【讨论】:

                  【解决方案23】:

                  我会在接受的答案中添加以下代码

                  public static class Retry<TException> where TException : Exception //ability to pass the exception type
                      {
                          //same code as the accepted answer ....
                  
                          public static T Do<T>(Func<T> action, TimeSpan retryInterval, int retryCount = 3)
                          {
                              var exceptions = new List<Exception>();
                  
                              for (int retry = 0; retry < retryCount; retry++)
                              {
                                  try
                                  {
                                      return action();
                                  }
                                  catch (TException ex) //Usage of the exception type
                                  {
                                      exceptions.Add(ex);
                                      Thread.Sleep(retryInterval);
                                  }
                              }
                  
                              throw new AggregateException(String.Format("Failed to excecute after {0} attempt(s)", retryCount), exceptions);
                          }
                      }
                  

                  基本上,上面的代码使Retry 类成为通用类,因此您可以传递要捕获的异常类型以进行重试。

                  现在几乎以相同的方式使用它,但指定异常类型

                  Retry<EndpointNotFoundException>.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));
                  

                  【讨论】:

                  • 即使 TRY CATCH 循环中的代码无异常执行,for 循环也将始终执行几次(基于您的 retryCount)。我建议将 retryCount 设置为等于 try 循环中的 retry var,这样 for 循环将停止遍历它。
                  • @scre_www 我相信你弄错了。如果action 没有抛出,那么Do 会返回,因此break 会远离for 循环。
                  • 无论如何,这个实现都有问题。在最后一次重试之后,就在放弃之前,Thread.Sleep 被无缘无故地调用。
                  【解决方案24】:

                  我知道这个答案已经很老了,但我只是想对此发表评论,因为我在使用这些 while、do、任何带有计数器的语句时遇到了问题。

                  多年来,我已经确定了一种我认为更好的方法。那就是使用某种事件聚合,例如响应式扩展“主题”等。当尝试失败时,您只需发布一个说明尝试失败的事件,并让聚合器函数重新安排该事件。这使您可以更好地控制重试,而不会因一堆重试循环而污染调用本身。你也不是用一堆线程睡眠来捆绑一个线程。

                  【讨论】:

                    【解决方案25】:

                    用 C#、Java 或其他语言做简单的事情:

                      internal class ShouldRetryHandler {
                        private static int RETRIES_MAX_NUMBER = 3;
                        private static int numberTryes;
                    
                        public static bool shouldRetry() {
                            var statusRetry = false;
                    
                            if (numberTryes< RETRIES_MAX_NUMBER) {
                                numberTryes++;
                                statusRetry = true;
                                //log msg -> 'retry number' + numberTryes
                    
                            }
                    
                            else {
                                statusRetry = false;
                                //log msg -> 'reached retry number limit' 
                            }
                    
                            return statusRetry;
                        }
                    }
                    

                    在你的代码中使用它非常简单:

                     void simpleMethod(){
                        //some code
                    
                        if(ShouldRetryHandler.shouldRetry()){
                        //do some repetitive work
                         }
                    
                        //some code    
                        }
                    

                    或者你可以在递归方法中使用它:

                    void recursiveMethod(){
                        //some code
                    
                        if(ShouldRetryHandler.shouldRetry()){
                        recursiveMethod();
                         }
                    
                        //some code    
                        }
                    

                    【讨论】:

                      【解决方案26】:
                      int retries = 3;
                      while (true)
                      {
                          try
                          {
                              //Do Somthing
                              break;
                          }
                          catch (Exception ex)
                          {
                              if (--retries == 0)
                                  return Request.BadRequest(ApiUtil.GenerateRequestResponse(false, "3 Times tried it failed do to : " + ex.Message, new JObject()));
                              else
                                  System.Threading.Thread.Sleep(100);
                          }
                      

                      【讨论】:

                      • 你用Request.BadRequest做什么?
                      【解决方案27】:
                      public delegate void ThingToTryDeletage();
                      
                      public static void TryNTimes(ThingToTryDelegate, int N, int sleepTime)
                      {
                         while(true)
                         {
                            try
                            {
                              ThingToTryDelegate();
                            } catch {
                      
                                  if( --N == 0) throw;
                                else Thread.Sleep(time);          
                            }
                      }
                      

                      【讨论】:

                      • 因为throw;是终止无限循环的唯一方法,所以这个方法实际上是实现“尝试直到它失败N次”而不是期望的“尝试到N次直到它成功”。在调用ThingToTryDelegate(); 之后,您需要一个break;return;,否则如果它永远不会失败,它将被连续调用。此外,这不会编译,因为TryNTimes 的第一个参数没有名称。 -1.
                      【解决方案28】:

                      我根据此处发布的答案编写了一个小班课程。希望它会帮助某人:https://github.com/natenho/resiliency

                      using System;
                      using System.Threading;
                      
                      /// <summary>
                      /// Classe utilitária para suporte a resiliência
                      /// </summary>
                      public sealed class Resiliency
                      {
                          /// <summary>
                          /// Define o valor padrão de número de tentativas
                          /// </summary>
                          public static int DefaultRetryCount { get; set; }
                      
                          /// <summary>
                          /// Define o valor padrão (em segundos) de tempo de espera entre tentativas
                          /// </summary>
                          public static int DefaultRetryTimeout { get; set; }
                      
                          /// <summary>
                          /// Inicia a parte estática da resiliência, com os valores padrões
                          /// </summary>
                          static Resiliency()
                          {
                              DefaultRetryCount = 3;
                              DefaultRetryTimeout = 0;
                          }
                      
                          /// <summary>
                          /// Executa uma <see cref="Action"/> e tenta novamente DefaultRetryCount vezes  quando for disparada qualquer <see cref="Exception"/> 
                          /// </summary>
                          /// <param name="action">Ação a ser realizada</param>
                          /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Não aguarda para realizar novas tentativa.</remarks>
                          public static void Try(Action action)
                          {
                              Try<Exception>(action, DefaultRetryCount, TimeSpan.FromMilliseconds(DefaultRetryTimeout), null);
                          }
                      
                          /// <summary>
                          /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
                          /// </summary>
                          /// <param name="action">Ação a ser realizada</param>
                          /// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
                          /// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
                          public static void Try(Action action, int retryCount, TimeSpan retryTimeout)
                          {
                              Try<Exception>(action, retryCount, retryTimeout, null);
                          }
                      
                          /// <summary>
                          /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
                          /// </summary>
                          /// <param name="action">Ação a ser realizada</param>
                          /// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
                          /// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
                          /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
                          public static void Try(Action action, int retryCount, TimeSpan retryTimeout, Action<ResiliencyTryHandler<Exception>> tryHandler)
                          {
                              Try<Exception>(action, retryCount, retryTimeout, tryHandler);
                          }
                      
                          /// <summary>
                          /// Executa uma <see cref="Action"/> e tenta novamente por até DefaultRetryCount vezes quando for disparada qualquer <see cref="Exception"/> 
                          /// </summary>
                          /// <param name="action">Ação a ser realizada</param>
                          /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
                          /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
                          public static void Try(Action action, Action<ResiliencyTryHandler<Exception>> tryHandler)
                          {
                              Try<Exception>(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
                          }
                      
                          /// <summary>
                          /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="TException"/> 
                          /// </summary>
                          /// <param name="action">Ação a ser realizada</param>
                          /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
                          public static void Try<TException>(Action action) where TException : Exception
                          {
                              Try<TException>(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
                          }
                      
                          /// <summary>
                          /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="TException"/> 
                          /// </summary>
                          /// <param name="action">Ação a ser realizada</param>
                          /// <param name="retryCount"></param>
                          public static void Try<TException>(Action action, int retryCount) where TException : Exception
                          {
                              Try<TException>(action, retryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), null);
                          }
                      
                          /// <summary>
                          /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
                          /// </summary>
                          /// <param name="action">Ação a ser realizada</param>
                          /// <param name="retryCount"></param>
                          /// <param name="retryTimeout"></param>
                          public static void Try<TException>(Action action, int retryCount, TimeSpan retryTimeout) where TException : Exception
                          {
                              Try<TException>(action, retryCount, retryTimeout, null);
                          }
                      
                          /// <summary>
                          /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada qualquer <see cref="Exception"/> 
                          /// </summary>
                          /// <param name="action">Ação a ser realizada</param>
                          /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
                          /// <remarks>Executa uma vez e realiza outras DefaultRetryCount tentativas em caso de exceção. Aguarda DefaultRetryTimeout segundos antes de realizar nova tentativa.</remarks>
                          public static void Try<TException>(Action action, Action<ResiliencyTryHandler<TException>> tryHandler) where TException : Exception
                          {
                              Try(action, DefaultRetryCount, TimeSpan.FromSeconds(DefaultRetryTimeout), tryHandler);
                          }
                      
                          /// <summary>
                          /// Executa uma <see cref="Action"/> e tenta novamente determinado número de vezes quando for disparada uma <see cref="Exception"/> definida no tipo genérico
                          /// </summary>
                          /// <param name="action">Ação a ser realizada</param>
                          /// <param name="retryCount">Número de novas tentativas a serem realizadas</param>
                          /// <param name="retryTimeout">Tempo de espera antes de cada nova tentativa</param>
                          /// <param name="tryHandler">Permitindo manipular os critérios para realizar as tentativas</param>
                          /// <remarks>Construído a partir de várias ideias no post <seealso cref="http://stackoverflow.com/questions/156DefaultRetryCount191/c-sharp-cleanest-way-to-write-retry-logic"/></remarks>
                          public static void Try<TException>(Action action, int retryCount, TimeSpan retryTimeout, Action<ResiliencyTryHandler<TException>> tryHandler) where TException : Exception
                          {
                              if (action == null)
                                  throw new ArgumentNullException(nameof(action));
                      
                              while (retryCount-- > 0)
                              {
                                  try
                                  {
                                      action();
                                      return;
                                  }
                                  catch (TException ex)
                                  {
                                      //Executa o manipulador de exception
                                      if (tryHandler != null)
                                      {
                                          var callback = new ResiliencyTryHandler<TException>(ex, retryCount);
                                          tryHandler(callback);
                                          //A propriedade que aborta pode ser alterada pelo cliente
                                          if (callback.AbortRetry)
                                              throw;
                                      }
                      
                                      //Aguarda o tempo especificado antes de tentar novamente
                                      Thread.Sleep(retryTimeout);
                                  }
                              }
                      
                              //Na última tentativa, qualquer exception será lançada de volta ao chamador
                              action();
                          }
                      
                      }
                      
                      /// <summary>
                      /// Permite manipular o evento de cada tentativa da classe de <see cref="Resiliency"/>
                      /// </summary>
                      public class ResiliencyTryHandler<TException> where TException : Exception
                      {
                          #region Properties
                      
                          /// <summary>
                          /// Opção para abortar o ciclo de tentativas
                          /// </summary>
                          public bool AbortRetry { get; set; }
                      
                          /// <summary>
                          /// <see cref="Exception"/> a ser tratada
                          /// </summary>
                          public TException Exception { get; private set; }
                      
                          /// <summary>
                          /// Identifca o número da tentativa atual
                          /// </summary>
                          public int CurrentTry { get; private set; }
                      
                          #endregion
                      
                          #region Constructors
                      
                          /// <summary>
                          /// Instancia um manipulador de tentativa. É utilizado internamente
                          /// por <see cref="Resiliency"/> para permitir que o cliente altere o
                          /// comportamento do ciclo de tentativas
                          /// </summary>
                          public ResiliencyTryHandler(TException exception, int currentTry)
                          {
                              Exception = exception;
                              CurrentTry = currentTry;
                          }
                      
                          #endregion
                      
                      }
                      

                      【讨论】:

                        【解决方案29】:

                        重试助手:一个通用的 java 实现,包含可返回和 void 类型的重试。

                        import java.util.function.Supplier;
                        import org.slf4j.Logger;
                        import org.slf4j.LoggerFactory;
                        
                        public class RetryHelper {
                          private static final Logger log = LoggerFactory.getLogger(RetryHelper.class);
                          private int retryWaitInMS;
                          private int maxRetries;
                        
                          public RetryHelper() {
                            this.retryWaitInMS = 300;
                            this.maxRetries = 3;
                          }
                        
                          public RetryHelper(int maxRetry) {
                            this.maxRetries = maxRetry;
                            this.retryWaitInMS = 300;
                          }
                        
                          public RetryHelper(int retryWaitInSeconds, int maxRetry) {
                            this.retryWaitInMS = retryWaitInSeconds;
                            this.maxRetries = maxRetry;
                          }
                        
                          public <T> T retryAndReturn(Supplier<T> supplier) {
                            try {
                              return supplier.get();
                            } catch (Exception var3) {
                              return this.retrySupplier(supplier);
                            }
                          }
                        
                          public void retry(Runnable runnable) {
                            try {
                              runnable.run();
                            } catch (Exception var3) {
                              this.retrySupplier(() -> {
                                runnable.run();
                                return null;
                              });
                            }
                        
                          }
                        
                          private <T> T retrySupplier(Supplier<T> supplier) {
                            log.error("Failed <TASK>, will be retried " + this.maxRetries + " times.");
                            int retryCounter = 0;
                        
                            while(retryCounter < this.maxRetries) {
                              try {
                                return supplier.get();
                              } catch (Exception var6) {
                                ++retryCounter;
                                log.error("<TASK> failed on retry: " + retryCounter + " of " + this.maxRetries + " with error: " + var6.getMessage());
                                if (retryCounter >= this.maxRetries) {
                                  log.error("Max retries exceeded.");
                                  throw var6;
                                }
                        
                                try {
                                  Thread.sleep((long)this.retryWaitInMS);
                                } catch (InterruptedException var5) {
                                  var5.printStackTrace();
                                }
                              }
                            }
                        
                            return supplier.get();
                          }
                        
                          public int getRetryWaitInMS() {
                            return this.retryWaitInMS;
                          }
                        
                          public int getMaxRetries() {
                            return this.maxRetries;
                          }
                        }
                        
                        

                        用法:

                            try {
                              returnValue = new RetryHelper().retryAndReturn(() -> performSomeTask(args));
                              //or no return type:
                              new RetryHelper().retry(() -> mytask(args));
                            } catch(Exception ex){
                              log.error(e.getMessage());
                              throw new CustomException();
                            }
                        

                        【讨论】:

                          【解决方案30】:

                          我已经像这样实现了已接受答案的异步版本 - 它似乎运行良好 - 任何 cmets?

                          
                                  public static async Task DoAsync(
                                      Action action,
                                      TimeSpan retryInterval,
                                      int maxAttemptCount = 3)
                                  {
                                      DoAsync<object>(() =>
                                      {
                                          action();
                                          return null;
                                      }, retryInterval, maxAttemptCount);
                                  }
                          
                                  public static async Task<T> DoAsync<T>(
                                      Func<Task<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 await action();
                                          }
                                          catch (Exception ex)
                                          {
                                              exceptions.Add(ex);
                                          }
                                      }
                                      throw new AggregateException(exceptions);
                                  }
                          
                          

                          而且,简单地这样称呼它:

                          var result = await Retry.DoAsync(() => MyAsyncMethod(), TimeSpan.FromSeconds(5), 4);
                          

                          【讨论】:

                          • Thread.Sleep?阻塞线程否定了异步的好处。我也很确定Task DoAsync() 版本应该接受Func&lt;Task&gt; 类型的参数。
                          猜你喜欢
                          • 1970-01-01
                          • 2015-09-14
                          • 1970-01-01
                          • 2011-06-03
                          • 1970-01-01
                          相关资源
                          最近更新 更多