You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

关键特性说明

  1. 灵活异常匹配:通过Func<Exception, bool>可以实现任意复杂的异常判断逻辑,例如:
    • 匹配特定异常类型+消息关键字
    • 匹配InnerException的类型或内容
    • 组合多种判断条件
  2. 按策略独立跟踪重试次数:每个策略维护自己的重试计数器,支持同一异常类型不同规则的独立控制
  3. 解耦评估逻辑:将异常判断、重试次数、延迟计算封装在策略类中,主方法仅负责执行循环和延迟逻辑
  4. 优化延迟计算:调整指数退避逻辑,首次重试使用基础延迟,避免初始延迟过大;随机退避使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 15:54:54