我们曾经遇到过这样一种情况,即每当出现这些异常时,我们必须满足某些异常,并重新尝试一种特定的方法。这在系统的各个部分都是必需的。此外,我们(可能)希望在需要重试时调用“回调”。
我将向您展示代码,然后给出一个使用示例:
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);
}因此,一个例子(虽然不是现实世界的例子)可能是:
ActionHelper.InvokeActionWithRetry(() => {
Print(document);
},
5,
() => {
RebootPrinter();
},
typeof(PrinterNeedsRestartException));我所写的静态方法有什么可以改进的吗?
我对使用actions/funcs非常陌生,所以我对如何重构它的建议持开放态度。
我也觉得类型评价有点“讨厌”。
我只是注意到,在调用Contains之前,我没有检查传入的类型集合。淘气。我会重做那个部分并更新问题。
发布于 2016-02-17 10:24:09
考虑一下这个例子:
InvokeActionWithRetry(() =>
{
Console.WriteLine("action");
throw new Exception();
},
3);它印了四次“行动”。这不是我对API的期望(而是我在阅读代码时所期望的)。我希望传入的数字是调用的总数,而不是第一次尝试后的重试次数。
您需要检查此方法的参数,因为有许多范围要传递到奇怪的值中。如果retryAttempts是一个负数,怎么办?那也太没道理了!
还需要检查action是否为null。
发布于 2016-02-17 13:35:28
您应该指定要重试的异常,而不是要失败的异常。这将防止您重新尝试意外/新引入的异常。
不要重复使用参数。
如果使用的是C# 6,则应该使用异常筛选,而不是使用catch {...throw;}。
根据您计划对其进行筛选的异常数量,从params创建一个HashSet<T>可能更好。
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();
}
}
}发布于 2016-02-17 14:41:03
1.功能扩展--除了前面提到的答案之外,我还会使用备份机制扩展您的功能,这在某些情况下(例如SQL死锁)是有用的。由于这些选项现在已经相当多,所以可以包含在一个特殊的类中:
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; }
}我还添加了一些默认值,使调用者更容易使用。
编辑按照RobH的建议将BackoffInterval从int改为TimeSpan
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();
}
}
}我考虑了一个简单的退步,从一个周期开始,然后乘以一个因子。
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();
}https://codereview.stackexchange.com/questions/120283
复制相似问题