首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >带回调的“重试”机制

带回调的“重试”机制
EN

Code Review用户
提问于 2016-02-17 10:03:28
回答 3查看 3.1K关注 0票数 5

我们曾经遇到过这样一种情况,即每当出现这些异常时,我们必须满足某些异常,并重新尝试一种特定的方法。这在系统的各个部分都是必需的。此外,我们(可能)希望在需要重试时调用“回调”。

我将向您展示代码,然后给出一个使用示例:

代码语言:javascript
复制
    public static void InvokeActionWithRetry(Action action, int retryAttempts, Action retryCallback = null, params Type[] exceptionTypes)
    {
        do
        {
            try {
                action();
                break;
            }
            catch (Exception ex)
            {
                   if ((exceptionTypes?.Length > 0 && exceptionTypes.Contains(ex.GetType()))
                        || retryAttempts == 0)
                    throw;

                if (retryCallback != null)
                    retryCallback();
            }

            retryAttempts--;
        }
        while (retryAttempts >= 0);
    }

因此,一个例子(虽然不是现实世界的例子)可能是:

代码语言:javascript
复制
ActionHelper.InvokeActionWithRetry(() => {
    Print(document);
},
5,
() => {
    RebootPrinter();
},
typeof(PrinterNeedsRestartException));

我所写的静态方法有什么可以改进的吗?

我对使用actions/funcs非常陌生,所以我对如何重构它的建议持开放态度。

我也觉得类型评价有点“讨厌”。

编辑:

我只是注意到,在调用Contains之前,我没有检查传入的类型集合。淘气。我会重做那个部分并更新问题。

EN

回答 3

Code Review用户

发布于 2016-02-17 10:24:09

考虑一下这个例子:

代码语言:javascript
复制
InvokeActionWithRetry(() => 
        { 
            Console.WriteLine("action"); 
            throw new Exception(); 
        }, 
    3);

它印了四次“行动”。这不是我对API的期望(而是我在阅读代码时所期望的)。我希望传入的数字是调用的总数,而不是第一次尝试后的重试次数。

您需要检查此方法的参数,因为有许多范围要传递到奇怪的值中。如果retryAttempts是一个负数,怎么办?那也太没道理了!

还需要检查action是否为null。

票数 3
EN

Code Review用户

发布于 2016-02-17 13:35:28

您应该指定要重试的异常,而不是要失败的异常。这将防止您重新尝试意外/新引入的异常。

不要重复使用参数。

如果使用的是C# 6,则应该使用异常筛选,而不是使用catch {...throw;}

根据您计划对其进行筛选的异常数量,从params创建一个HashSet<T>可能更好。

代码语言:javascript
复制
public static void InvokeActionWithRetry(Action action, int attempts, Action retryCallback = null, params Type[] exceptionTypes)
{
    if(action == null) throw new ArgumentNullException(nameof(action));
    if(attempts < 0) throw new ArgumentOutOfRangeException(nameof(attempts), nameof(attempts) + " must be positive");

    var exceptionFilter = (exceptionTypes?.Length > 0) 
        ? new HashSet<Type>(exceptionTypes) 
        : new HashSet<Type>();

    var lastAttempt = attempts - 1;
    for(int attempt = 0; attempt < attempts; attempt++)
    {
        try {
            action();
            break;
        }
        catch (Exception ex) when (exceptionFilter.Contains(ex.GetType()) && attempt != lastAttempt)
        {
            if (retryCallback != null)
                retryCallback();
        }
    }
}
票数 3
EN

Code Review用户

发布于 2016-02-17 14:41:03

1.功能扩展--除了前面提到的答案之外,我还会使用备份机制扩展您的功能,这在某些情况下(例如SQL死锁)是有用的。由于这些选项现在已经相当多,所以可以包含在一个特殊的类中:

代码语言:javascript
复制
    public class ActionRetryOptions
    {
        // general data
        public Action Action { get; set; }
        public uint AttemptCount { get; set; } = 3;

        // retry data
        public Action RetryCallback { get; set; }

        // backoff mechanism
        public bool UseBackoff { get; set; } = false;
        public TimeSpan BackoffInitialInterval { get; set; } = TimeSpan.FromSeconds(1);
        public int BackoffFactor { get; set; } = 1;

        // fail fast
        public IList<Type> FailFastExceptionTypes { get; set; }
    }

我还添加了一些默认值,使调用者更容易使用。

2.更改函数以支持回退

编辑按照RobH的建议将BackoffIntervalint改为TimeSpan

代码语言:javascript
复制
    public static void InvokeActionWithRetry(ActionRetryOptions retryOptions)
    {
        if (retryOptions == null)
            throw new ArgumentNullException(nameof(retryOptions));

        if (retryOptions.Action == null)
            throw new ArgumentNullException(nameof(retryOptions.Action));

        if (retryOptions.BackoffFactor < 1)
            throw new ArgumentException("BackoffInterval must greater or equal to 1");

        // backoff initialization
        int backOffTime = (int) retryOptions.BackoffInitialInterval.TotalMilliseconds;
        var random = new Random();

        for (uint attempt = retryOptions.AttemptCount; attempt > 0; attempt --)
        {
            try
            {
                retryOptions.Action();
                break;
            }
            catch (Exception ex)
            {
                if ((retryOptions.FailFastExceptionTypes?.Count > 0 && retryOptions.FailFastExceptionTypes.Contains(ex.GetType()))
                     || attempt <= 1)
                    throw;

                // back-off
                if (retryOptions.UseBackoff)
                {
                    int sleepTime = (int)(backOffTime * random.NextDouble());
                    Thread.Sleep(sleepTime);
                    backOffTime *= retryOptions.BackoffFactor;
                }

                if (retryOptions.RetryCallback != null)
                    retryOptions.RetryCallback();
            }
        }
    }

我考虑了一个简单的退步,从一个周期开始,然后乘以一个因子。

3.简单测试

代码语言:javascript
复制
   static void Main(string[] args)
   {
        try
        {
            var retryOptions = new ActionRetryOptions()
            {
                Action = () =>
                    {
                        Console.WriteLine("Action with error");
                        throw new Exception();
                    },
                UseBackoff = true,
                BackoffFactor = 2,
                AttemptCount = 5,
                RetryCallback = () =>
                {
                    Console.WriteLine("Retry callback");
                },
                FailFastExceptionTypes = new List<Type>() { typeof(SqlException) }
            };

            InvokeActionWithRetry(retryOptions);
        }
        catch (Exception exc)
        {
            Console.WriteLine("Unhandled exception - " + exc.ToString());
        }
        Console.ReadLine();
    }
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/120283

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档