写重试逻辑的最干净的方法?

有时,我需要在放弃之前将一个操作重试几次。我的代码是:

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

我想在一个通用的重试函数中重写这个:

TryThreeTimes(DoSomething);

这在c#中可行吗?TryThreeTimes()方法的代码是什么?

327269 次浏览
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);
}
}
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);

如果将简单地重试同一个调用的通用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过载。

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

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

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

或者做得更整洁一点....

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

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

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

我要实现这个:

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

因此,我可能会将它用于如下代码:

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.");
}

如果传递一个方法不使用的参数被证明是笨拙的,那么实现一个只接受Func<bool>Retry重载是很简单的。

这可能是个坏主意。首先,它象征着“疯狂的定义是做同一件事两次,每次都期待不同的结果”这句格言。其次,这种编码模式本身不能很好地组合。例如:

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

现在假设软件层在包失败时重发关于失败的通知三次。

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

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

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

现在假设web客户端在从服务器得到错误后重新发送请求三次。

现在假设网络交换机上用来将通知路由到管理员的线路被拔掉。web客户端的用户什么时候最终得到错误消息?我大约12分钟后到。

以免您认为这只是一个愚蠢的例子:我们已经在客户代码中看到了这个错误,尽管比我在这里描述的要严重得多。在特定的客户代码中,错误条件发生和最终报告给用户之间的间隔是,因为有很多层自动重试等待。想象一下,如果是十个而不是三个的重试会发生什么。

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

允许函数和重试消息

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

在之前工作的基础上,我考虑用三种方式增强重试逻辑:

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

The method can then be invoked like so (anonymous methods can be used as well, of course):

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

我需要传递一些参数给我的方法重试,并有一个结果值;所以我需要一个表达。 我建立了这个类来做这个工作(它是受布什金的启发) 你可以这样使用它:

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();
}
}
< p > p。 LBushkin的解再重试一次=D

暂态故障处理应用程序块提供了一个可扩展的重试策略集合,包括:

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

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

有关更多信息,请参阅开发人员指南中的这一章

可通过NuGet(搜索“黄玉”)。

我需要一个支持取消的方法,在此过程中,我添加了返回中间故障的支持。

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.
}

或者,除非取消,否则每隔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函数,以支持我希望使用的不同委托类型。

我是递归和扩展方法的粉丝,所以这里是我的观点:

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


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

你应该试试波利<强> < / >强。它是由我编写的。net库,允许开发人员以流畅的方式表达临时异常处理策略,如重试,永远重试,等待和重试或断路器。

例子

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

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<dynamic>代替Func<T>

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

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

用波利

https://github.com/App-vNext/Polly-Samples < a href = " https://github.com/App-vNext/Polly-Samples " > < / >

这是我和波莉一起用的试药

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

我知道这个答案很老了,但我只是想对此进行评论,因为我在使用这些while, do, whatever语句时遇到了问题。

多年来,我认为我找到了一个更好的方法。这就是使用某种事件聚合,如响应式扩展“Subject”或类似的。当尝试失败时,只需发布一个事件,说明尝试失败,并让聚合器函数重新调度该事件。这使您可以更好地控制重试,而不会用一堆重试循环或其他东西污染调用本身。你也不是用一堆线程来捆绑一个线程。

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

指数倒扣是一个很好的重试策略,而不是简单地尝试x次。您可以使用Polly这样的库来实现它。

对于那些既想对任何异常进行重试,又想显式设置异常类型的人,可以使用以下方法:

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

我根据这里发布的答案写了一个小课。希望它能帮助到一些人: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


}

使用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);
}
}

以最新的方式实现了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

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

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

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

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

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

/// <seealso href="https://learn.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 );
}

我已经实现了一个异步版本的接受的答案,就像这样-它似乎工作得很好-有评论吗?


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

我使用Polly实现了该模式的两个实现。其一是异步。

我的同步方法是基于这个答案 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也很容易。