Cleanest way to write retry logic?
有时候,我需要在放弃之前重试几次手术。我的代码是:
1 2 3 4 5 6 7 8 9 10 | int retries = 3; while(true) { try { DoSomething(); break; // success! } catch { if(--retries == 0) throw; else Thread.Sleep(1000); } } |
我想用一个一般的重试函数重写这个函数,比如:
1 | TryThreeTimes(DoSomething); |
有没有可能用C?
如果用作一般的异常处理机制,则只重试同一调用的覆盖catch语句可能会很危险。尽管如此,这里有一个基于lambda的重试包装器,可以用于任何方法。我选择将重试次数和重试超时作为参数,以获得更大的灵活性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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); } } |
现在可以使用此实用程序方法执行重试逻辑:
1 | Retry.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1)); |
或:
1 | Retry.Do(SomeFunctionThatCanFail, TimeSpan.FromSeconds(1)); |
或:
1 | int result = Retry.Do(SomeFunctionWhichReturnsInt, TimeSpan.FromSeconds(1), 4); |
或者你甚至可以让
你应该试试波莉。它是一个由我编写的.NET库,它允许开发人员以流畅的方式表达短暂的异常处理策略,如重试、永远重试、等待和重试或断路器。
例子1 2 3 4 5 | Policy .Handle<SqlException>(ex => ex.Number == 1205) .Or<ArgumentException>(ex => ex.ParamName =="example") .WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(3)) .Execute(() => DoSomething()); |
这可能是个坏主意。首先,它是"精神错乱的定义是两次做同一件事,每次都期待不同的结果"这句格言的象征。第二,这种编码模式本身并不能很好地组合。例如:
假设您的网络硬件层在失败时重新发送数据包三次,例如,在失败之间等待一秒钟。
现在假设软件层在数据包失败时三次重新发送关于失败的通知。
现在假设通知层在通知传递失败时三次重新激活通知。
现在假设错误报告层在通知失败时重新激活通知层三次。
现在假设Web服务器在错误失败时重新激活错误报告三次。
现在假设Web客户机在从服务器得到一个错误时重新发送请求三次。
现在假设网络交换机上应该将通知路由到管理员的线路已拔出。Web客户端的用户何时终于收到错误消息?我大约十二分钟后赶到。
以免你认为这只是一个愚蠢的例子:我们在客户代码中看到了这个bug,尽管远比我在这里描述的要糟糕得多。在特定的客户代码中,发生的错误条件与最终向用户报告的错误条件之间的间隔是几周,因为有那么多层会自动用等待重试。想象一下如果有10次而不是3次的重试会发生什么。
通常,处理错误情况的正确方法是立即报告并让用户决定要做什么。如果用户想要创建一个自动重试策略,让他们在软件抽象的适当级别上创建该策略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public void TryThreeTimes(Action action) { var tries = 3; while (true) { try { action(); break; // success! } catch { if (--tries == 0) throw; Thread.Sleep(1000); } } } |
然后你会打电话给:
1 | TryThreeTimes(DoSomething); |
…或者…
1 | TryThreeTimes(() => DoSomethingElse(withLocalVariable)); |
更灵活的选择:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
用作:
1 | DoWithRetry(DoSomething, TimeSpan.FromSeconds(2), tryCount: 10); |
更现代的版本,支持异步/等待:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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); } } } |
用作:
1 | await DoWithRetryAsync(DoSomethingAsync, TimeSpan.FromSeconds(2), tryCount: 10); |
瞬态故障处理应用程序块提供了可扩展的重试策略集合,包括:
- 增量
- 固定区间
- 指数后退
它还包括基于云的服务的错误检测策略集合。
有关更多信息,请参阅《开发人员指南》的本章。
通过nuget提供(搜索"黄玉")。
允许函数和重试消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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的测试中都有类似的问题,当给定负值时,重试无穷大或未能引发异常。另外,在上面的catch块中,sleep(-1000)也会失败。取决于你对人们的期望有多"愚蠢",但是防御编程永远不会伤害到你。
我喜欢递归和扩展方法,下面是我的两点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public static void InvokeWithRetries(this Action @this, ushort numberOfRetries) { try { @this(); } catch { if (numberOfRetries == 0) throw; InvokeWithRetries(@this, --numberOfRetries); } } |
在前面的工作的基础上,我考虑通过三种方式增强重试逻辑:
使之成为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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( ); } } |
然后可以这样调用该方法(当然,也可以使用匿名方法):
1 2 | new Action( AMethodThatMightThrowIntermittentException ) .InvokeAndRetryOnException<IntermittentException>( 2, TimeSpan.FromSeconds( 1 ) ); |
使用波利
https://github.com/app-vnext/polly-samples
这里有一个我和波利一起使用的重试通用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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; } |
像这样用
1 | var result = Retry(() => MyFunction()), 3); |
用C 6.0保持简单
1 2 3 4 5 6 7 8 9 10 11 12 | 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的答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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); } |
并使用它:
1 | await Retry.Do([TaskFunction], retryInterval, retryAttempts); |
而函数
我会实施这个:
1 2 3 4 5 6 7 8 9 10 11 12 | public static bool Retry(int maxRetries, Func<bool, bool> method) { while (maxRetries > 0) { if (method(maxRetries == 1)) { return true; } maxRetries--; } return false; } |
我不会像在其他示例中那样使用异常。在我看来,如果我们期望某个方法不会成功,那么它的失败也不例外。所以我调用的方法如果成功应该返回true,如果失败则返回false。
为什么它是一个
因此,我可能会将它与以下代码一起使用:
1 2 3 4 5 6 7 8 9 | Retry(5, delegate(bool lastIteration) { // do stuff if (!succeeded && lastIteration) { throw new InvalidOperationException(...) } return succeeded; }); |
或
1 2 3 4 5 6 7 8 | if (!Retry(5, delegate(bool lastIteration) { // do stuff return succeeded; })) { Console.WriteLine("Well, that didn't work."); } |
如果传递一个方法不使用的参数被证明是很困难的,那么实现一个只需要一个
对于希望同时具有对任何异常重试或显式设置异常类型的选项的用户,请使用以下选项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | 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); } } |
我的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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); } |
要点:我用
指数退避是一种很好的重试策略,而不是简单地尝试x次。您可以使用像polly这样的库来实现它。
我需要一个支持取消的方法,当我在这里的时候,我添加了对返回中间故障的支持。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | 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(); } } |
您可以这样使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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. } |
或者,每隔五秒钟重试一次,除非取消。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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. } |
您可以猜到,在我的源代码中,我已经重载了
或者稍微整理一下……
1 2 3 4 5 6 7 8 9 10 11 12 | int retries = 3; while (retries > 0) { if (DoSomething()) { retries = 0; } else { retries--; } } |
我相信抛出异常通常应该避免作为一种机制,除非您的A在边界之间传递它们(例如构建其他人可以使用的库)。如果成功,那么为什么不让
编辑:这可以像其他人建议的那样封装在一个函数中。唯一的问题是如果您不自己编写
我需要将一些参数传递给我的方法以重试,并有一个结果值;所以我需要一个表达式。我建立了这门课来完成这项工作(这门课的灵感来自于《易卜生的一个》)。您可以这样使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 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
用C语言、Java语言或其他语言做简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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; } } |
在代码中使用它非常简单:
1 2 3 4 5 6 7 8 9 | void simpleMethod(){ //some code if(ShouldRetryHandler.shouldRetry()){ //do some repetitive work } //some code } |
或者可以在递归方法中使用它:
1 2 3 4 5 6 7 8 9 | void recursiveMethod(){ //some code if(ShouldRetryHandler.shouldRetry()){ recursiveMethod(); } //some code } |
我知道这个答案很古老,但我只是想对此发表评论,因为我在使用这些while,do,whatever语句时遇到了问题。
多年来,我决定采用一种更好的方法。那就是使用某种类型的事件聚合,比如反应性扩展"主题"之类的。当一次尝试失败时,您只需发布一个表示该尝试失败的事件,并让聚合函数重新调度该事件。这允许您对重试进行更多的控制,而不必使用一系列的重试循环来污染调用本身。你也不是用一束线把一根线捆起来睡觉。
我将在接受的答案中添加以下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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); } } |
基本上,上面的代码使
现在几乎以相同的方式使用它,但是指定异常类型
1 | Retry<EndpointNotFoundException>.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1)); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); } } |
我根据这里的答案写了一个小班。希望它能帮助某人:https://github.com/natenho/resiliency
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | 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 } |