.NET中支持可配置参数的异常重试模式优化需求
增强Retry模式实现:支持灵活异常评估与按异常类型跟踪重试次数
需求背景
需要增强Retry模式实现,满足以下要求:
- 主方法(RetryAsync)执行目标操作,捕获异常后交给异常评估器处理
- 评估器需返回合适的等待延迟时长,同时按异常类型独立跟踪重试次数(如ConnectionLostException重试2次、DatabaseInaccessibleException重试3次)
- 现有实现仅支持按异常类型全名匹配,无法灵活检查异常消息、InnerException等细节,需补充该能力
现有基础实现
public enum IntervalGrowthRate { None, Linear, Exponential, Random }; public static async Task<T> RetryAsync<T>( Func<Task<T>> action, IDictionary<string, (IntervalGrowthRate, int, int)> retrySettings) { int waitMs = 0; int totalAttempts = 0; Exception lastException = null; IDictionary<string, int> retryAttempts = new Dictionary<string, int>(); while (true) { try { await Task.Delay(waitMs); return await action().ConfigureAwait(false); } catch (Exception ex) { var exceptionName = ex.GetType().FullName; if (retrySettings.TryGetValue(exceptionName, out var settings)) { var intervalRate = settings.Item1; var retryInterval = settings.Item2; var retryCount = settings.Item3; lastException = ex; retryAttempts.TryGetValue(exceptionName, out int currentAttempt); retryAttempts[exceptionName] = ++currentAttempt; if (currentAttempt <= retryCount) { waitMs = CalculateDelay(intervalRate, retryInterval, currentAttempt); Logging.LogError("Hit an exception and will retry: {0}", activityId, ex.ToString()); totalAttempts++; } else break; } else throw; } } var exceptionMessage = string.Format($"{action.Method.Name} method execution failed after retrying {totalAttempts} times."); throw new Exception(exceptionMessage, lastException); } private static int CalculateDelay(IntervalGrowthRate growthRate, int delayMs, int currentAttempt) { // No delay necessary before the first attempt if (currentAttempt < 1) { return 0; } switch (growthRate){ case IntervalGrowthRate.Linear : return delayMs * currentAttempt; case IntervalGrowthRate.Exponential : return delayMs * (int)Math.Pow(2, currentAttempt); case IntervalGrowthRate.Random : return (int)(delayMs * currentAttempt * (1 + new Random().NextDouble())); case IntervalGrowthRate.None : default : return delayMs; }; }
重构解决方案
核心改进点
引入异常重试策略类,将异常匹配逻辑、重试次数控制、延迟计算解耦,实现灵活的异常评估能力:
重构后的完整代码
public enum IntervalGrowthRate { None, Linear, Exponential, Random } /// <summary> /// 异常重试策略:封装异常匹配规则、重试次数、延迟策略 /// </summary> public class ExceptionRetryPolicy { /// <summary> /// 异常匹配器:判断当前异常是否符合该策略 /// </summary> public Func<Exception, bool> ExceptionMatcher { get; } /// <summary> /// 延迟增长策略 /// </summary> public IntervalGrowthRate GrowthRate { get; } /// <summary> /// 基础延迟时长(毫秒) /// </summary> public int BaseDelayMs { get; } /// <summary> /// 最大重试次数 /// </summary> public int MaxRetryCount { get; } public ExceptionRetryPolicy( Func<Exception, bool> exceptionMatcher, IntervalGrowthRate growthRate, int baseDelayMs, int maxRetryCount) { ExceptionMatcher = exceptionMatcher ?? throw new ArgumentNullException(nameof(exceptionMatcher)); GrowthRate = growthRate; BaseDelayMs = baseDelayMs; MaxRetryCount = maxRetryCount; } } public static class RetryHelper { public static async Task<T> RetryAsync<T>( Func<Task<T>> action, IEnumerable<ExceptionRetryPolicy> retryPolicies) { if (action == null) throw new ArgumentNullException(nameof(action)); if (retryPolicies == null) throw new ArgumentNullException(nameof(retryPolicies)); var policies = retryPolicies.ToList(); // 按策略跟踪重试次数 var policyRetryCounts = new Dictionary<ExceptionRetryPolicy, int>(); int totalAttempts = 0; Exception lastException = null; int waitMs = 0; while (true) { try { await Task.Delay(waitMs).ConfigureAwait(false); return await action().ConfigureAwait(false); } catch (Exception ex) { lastException = ex; // 找到匹配的第一个策略 var matchedPolicy = policies.FirstOrDefault(p => p.ExceptionMatcher(ex)); if (matchedPolicy == null) { // 无匹配策略,直接抛出异常 throw; } // 更新当前策略的重试次数 policyRetryCounts.TryGetValue(matchedPolicy, out int currentAttempt); currentAttempt++; policyRetryCounts[matchedPolicy] = currentAttempt; if (currentAttempt > matchedPolicy.MaxRetryCount) { // 达到最大重试次数,退出循环 break; } // 计算延迟时长 waitMs = CalculateDelay(matchedPolicy.GrowthRate, matchedPolicy.BaseDelayMs, currentAttempt); Logging.LogError("Hit an exception and will retry after {0}ms: {1}", waitMs, ex.ToString()); totalAttempts++; } } var exceptionMessage = $"{action.Method.Name} method execution failed after retrying {totalAttempts} times."; throw new InvalidOperationException(exceptionMessage, lastException); } private static int CalculateDelay(IntervalGrowthRate growthRate, int delayMs, int currentAttempt) { if (currentAttempt < 1) return 0; switch (growthRate) { case IntervalGrowthRate.Linear: return delayMs * currentAttempt; case IntervalGrowthRate.Exponential: return delayMs * (int)Math.Pow(2, currentAttempt - 1); // 调整为首次重试用基础延迟,避免指数过大 case IntervalGrowthRate.Random: var random = new Random(Guid.NewGuid().GetHashCode()); // 避免多线程下随机值重复 return (int)(delayMs * currentAttempt * (1 + random.NextDouble())); case IntervalGrowthRate.None: default: return delayMs; } } }
关键特性说明
- 灵活异常匹配:通过
Func<Exception, bool>可以实现任意复杂的异常判断逻辑,例如:- 匹配特定异常类型+消息关键字
- 匹配InnerException的类型或内容
- 组合多种判断条件
- 按策略独立跟踪重试次数:每个策略维护自己的重试计数器,支持同一异常类型不同规则的独立控制
- 解耦评估逻辑:将异常判断、重试次数、延迟计算封装在策略类中,主方法仅负责执行循环和延迟逻辑
- 优化延迟计算:调整指数退避逻辑,首次重试使用基础延迟,避免初始延迟过大;随机退避使用Guid生成随机种子,避免多线程场景下随机值重复
调用示例
// 定义重试策略集合 var retryPolicies = new List<ExceptionRetryPolicy> { // 匹配DivideByZeroException,指数退避,最多重试2次 new ExceptionRetryPolicy( ex => ex is DivideByZeroException, IntervalGrowthRate.Exponential, 1000, 2 ), // 匹配OverflowException,线性退避,最多重试3次 new ExceptionRetryPolicy( ex => ex is OverflowException, IntervalGrowthRate.Linear, 3000, 3 ), // 匹配SqlException且消息包含"连接失败",随机退避,最多重试4次 new ExceptionRetryPolicy( ex => ex is SqlException sqlEx && sqlEx.Message.Contains("连接失败"), IntervalGrowthRate.Random, 2000, 4 ), // 匹配任意包含InnerException为TimeoutException的异常,固定延迟,最多重试1次 new ExceptionRetryPolicy( ex => ex.InnerException is TimeoutException, IntervalGrowthRate.None, 5000, 1 ) }; // 调用重试方法 var result = await RetryHelper.RetryAsync( async () => { // 执行可能抛出异常的操作 await Task.Delay(100); // 模拟异常:取消注释测试 // throw new DivideByZeroException(); return "Success"; }, retryPolicies );
内容的提问来源于stack exchange,提问作者murick
相关产品推荐
相关产品推荐

