Question

We currently have a naive RetryWrapper which retries a given func upon the occurrence of an exception:

public T Repeat<T, TException>(Func<T> work, TimeSpan retryInterval, int maxExecutionCount = 3) where TException : Exception
{ 
   ... 

And for the retryInterval we are using the below logic to "wait" before the next attempt.

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

I don't particularly like this logic, also ideally I would prefer the retry logic NOT to happen on the main thread, can you think of a better way?

Note: I am happy to consider answers for .Net >= 3.5

Was it helpful?

Solution

So long as your method signature returns a T, the main thread will have to block until all retries are completed. However, you can reduce CPU by having the thread sleep instead of doing a manual reset event:

Thread.Sleep(retryInterval);

If you are willing to change your API, you can make it so that you don't block the main thread. For example, you could use an async method:

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

This can be consumed synchronously with:

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

However, you can also start the task and then wait for it later:

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 */ }
});

OTHER TIPS

Consider using Transient Fault Handling Application Block

The Microsoft Enterprise Library Transient Fault Handling Application Block lets developers make their applications more resilient by adding robust transient fault handling logic. Transient faults are errors that occur because of some temporary condition such as network connectivity issues or service unavailability. Typically, if you retry the operation that resulted in a transient error a short time later, you find that the error has disappeared.

It is available as a NuGet package.

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

How about using a timer instead of stopwatch?

For example:

    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
        }   
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top