如何在C#中实现最佳的重试包装器?

4
我们目前有一个天真的RetryWrapper,它会在异常发生时重试给定的函数:
public T Repeat<T, TException>(Func<T> work, TimeSpan retryInterval, int maxExecutionCount = 3) where TException : Exception
{ 
   ... 

对于retryInterval,我们使用以下逻辑在下一次尝试之前进行“等待”。

_stopwatch.Start();
while (_stopwatch.Elapsed <= retryInterval)
{
  // do nothing but actuallky it does! lots of CPU usage specially if retryInterval is high
}
_stopwatch.Reset();

我不特别喜欢这个逻辑,理想情况下我希望重试逻辑不要在主线程上发生,你能想到更好的方法吗?

注意:我很乐意考虑 .Net >= 3.5 的答案。


可能是[如何等待一段时间或函数调用,无论哪个花费更长的时间,即使系统时间改变?]的重复问题(http://stackoverflow.com/questions/5107522/how-to-wait-for-a-period-of-time-or-function-call-whichever-takes-longest-even)。 - CodeCaster
3个回答

3
只要您的方法签名返回一个T,主线程就必须阻塞,直到所有重试都完成。但是,您可以通过让线程休眠而不是执行手动重置事件来减少CPU使用率:
Thread.Sleep(retryInterval);

如果您愿意更改API,您可以使其不阻止主线程。例如,您可以使用异步方法:

public async Task<T> RepeatAsync<T, TException>(Func<T> work, TimeSpan retryInterval, int maxExecutionCount = 3) where TException : Exception
{
     for (var i = 0; i < maxExecutionCount; ++i)
     {
        try { return work(); }
        catch (TException ex)
        {
            // allow the program to continue in this case
        }
        // this will use a system timer under the hood, so no thread is consumed while
        // waiting
        await Task.Delay(retryInterval);
     }
}

这可以通过同步方式进行消费:

RepeatAsync<T, TException>(work, retryInterval).Result;

然而,您也可以开始任务,然后稍后等待它完成:
var task = RepeatAsync<T, TException>(work, retryInterval);

// do other work here

// later, if you need the result, just do
var result = task.Result;
// or, if the current method is async:
var result = await task;

// alternatively, you could just schedule some code to run asynchronously
// when the task finishes:
task.ContinueWith(t => {
    if (t.IsFaulted) { /* log t.Exception */ }
    else { /* success case */ }
});

2
考虑使用瞬态故障处理应用程序块
微软企业库瞬态故障处理应用程序块可让开发人员通过添加强大的瞬态故障处理逻辑使其应用程序更具有弹性。瞬态故障是由于某些临时条件(如网络连接问题或服务不可用)而导致的错误。通常,如果您在短时间内重试导致瞬态错误的操作,则会发现错误已经消失了。
该应用程序块可作为NuGet包提供。
using Microsoft.Practices.TransientFaultHandling;
using Microsoft.Practices.EnterpriseLibrary.WindowsAzure.TransientFaultHandling;
...
// Define your retry strategy: retry 5 times, starting 1 second apart
// and adding 2 seconds to the interval each retry.
var retryStrategy = new Incremental(5, TimeSpan.FromSeconds(1), 
  TimeSpan.FromSeconds(2));

// Define your retry policy using the retry strategy and the Windows Azure storage
// transient fault detection strategy.
var retryPolicy =
  new RetryPolicy<StorageTransientErrorDetectionStrategy>(retryStrategy);

// Receive notifications about retries.
retryPolicy.Retrying += (sender, args) =>
    {
        // Log details of the retry.
        var msg = String.Format("Retry - Count:{0}, Delay:{1}, Exception:{2}",
            args.CurrentRetryCount, args.Delay, args.LastException);
        Trace.WriteLine(msg, "Information");
    };

try
{
  // Do some work that may result in a transient fault.
  retryPolicy.ExecuteAction(
    () =>
    {
        // Your method goes here!
    });
}
catch (Exception)
{
  // All the retries failed.
}

0

使用计时器代替秒表怎么样?

例如:

    TimeSpan retryInterval = new TimeSpan(0, 0, 5);
    DateTime startTime;
    DateTime retryTime;
    Timer checkInterval = new Timer();

    private void waitMethod()
    {
        checkInterval.Interval = 1000;
        checkInterval.Tick += checkInterval_Tick;         
        startTime = DateTime.Now;
        retryTime = startTime + retryInterval;
        checkInterval.Start();
    }

    void checkInterval_Tick(object sender, EventArgs e)
    {
        if (DateTime.Now >= retryTime)
        {
            checkInterval.Stop();

            // Retry Interval Elapsed
        }   
    }

1
这仍然是一个自旋等待,会在阻塞主线程的同时消耗大量CPU。此外,由于您不断重新分配startTime,因此它将永远运行。 - ChaseMedallion
显然,它需要在一个函数中,这只是伪代码。 - Scott Solmer
1
@Okuma 是的,这是伪代码,但即使是这个伪代码,也会一直运行下去,因为当retryInterval不等于零时, startTime <= startTime + retryInterval 总是为真。 - Maarten
1
@Maarten 你说得对,我犯了一个错误。希望我的修改能够改进。 - Scott Solmer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接