Mike
Mike

Reputation: 411

General purpose Try and Retry with a Timeout in C#?

I'm looking for a general purpose try and retry with a timeout in C#. Basically, I want the following:

bool stopTrying = false;
DateTime time = DateTime.Now;
while (!stopTrying)
{
    try
    {
        //[Statement to Execute]
    }
    catch (Exception ex)
    {
        if (DateTime.Now.Subtract(time).Milliseconds > 10000)
        {
            stopTrying = true;
            throw ex;
        }
    }
}

In the case above, I'm waiting for 10 second, but it should be a variable timeout based on a parameter. I don't want to have to repeat this full code wherever I need to use it. There are multiple places in my code where they isn't a timeout built into the API and I'll hit an exception if the application isn't ready for the statement to execute. This would also avoid having to hardcode delays in my application before these satement.

Clarification: The statement in question could be something like an assignment. If I use a delegate and method.Invoke, isn't the invokation scoped inside the delegate and not the original method?

Upvotes: 8

Views: 18216

Answers (7)

alelom
alelom

Reputation: 2991

This is the solution I'm using, an improved version of this answer.

public static class ExecutionExtensions
{
    public static object? InvokeWithRetry(Delegate method, TimeSpan timeout, bool exceptionOnTimeout = false, params object?[] parameters)
    {
        DateTime startTime = DateTime.Now;
        while (true)
        {
            try
            {
                return method.DynamicInvoke(parameters);
            }
            catch (Exception ex)
            {
                if (DateTime.Now - startTime > timeout)
                    if (exceptionOnTimeout)
                        throw;
                    else
                        return default;
            }
        }
    }
}

Example usage:

public class SparqlReader
{
    delegate SparqlResultSet DelegateQuery(string query);

    protected SparqlRemoteEndpoint sparqlRemoteEndpoint;

    public SparqlResultSet GetResult(string query)
    {
        return ExecutionExtensions.DoOrTimeout(new DelegateQuery(sparqlRemoteEndpoint.QueryWithResultSet), new TimeSpan(0, 0, 30), parameters: query) as SparqlResultSet;
    }

    ...
}

Upvotes: -1

Alex
Alex

Reputation: 1

This code is wrong (infinite loop):

if (DateTime.Now.Subtract(time).Milliseconds > 10000)

The right one is:

if (DateTime.Now.Subtract(time).TotalMilliseconds > 10000)

Upvotes: 0

fijiaaron
fijiaaron

Reputation: 5185

Here's a simple solution:

long TIMEOUT = 60000; // 1 minute
long INTERVAL = 1000; // 1 second

System.DateTime startTime = System.DateTime.Now;    

while (check_condition())
{
    System.Threading.Thread.Sleep(INTERVAL);
    long elapsedTime = System.DateTime.Now.Millisecond - startTime.Millisecond;

    if (elapsedTime > TIMEOUT)
    {
        throw new Exception("Timeout exceeded");
    }
}

Upvotes: 0

strager
strager

Reputation: 90012

Using your example, the solution is simple:

bool DoOrTimeout<T>(T method, TimeSpan timeout) where T : delegate // FIXME
{
    bool stopTrying = false;
    DateTime time = DateTime.Now;
    while (!stopTrying)
    {
        try
        {
            method.Invoke();
            stopTrying = true;
        }
        catch (Exception ex)
        {
            if (DateTime.Now.Subtract(time).Milliseconds > timeout.TotalMilliseconds)
            {
                stopTrying = true;
                throw;
            }
        }
    }
}

Just call DoOrTimeout with a delegate as the first parameter.

Upvotes: 15

chilltemp
chilltemp

Reputation: 8962

Take a look at this question. What your asking for is exactly one of the uses I intended.
Implement C# Generic Timeout

WARNING: This sample uses Thread.Abort. Follow the link to my original question to read a few warnings about that in the comments.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Something
{
  public static class TimeoutWrapper
  {
    public static void Invoke(TimeSpan timeout, Action action)
    {
      Invoke(timeout, action, null);
    }
    public static void Invoke(TimeSpan timeout, Action action, Action abort)
    {
      Thread threadToKill = null;
      Action wrappedAction = () =>
      {
        threadToKill = Thread.CurrentThread;
        action();
      };

      IAsyncResult result = wrappedAction.BeginInvoke(null, null);
      if (result.AsyncWaitHandle.WaitOne(timeout, true))
      {
        wrappedAction.EndInvoke(result);
      }
      else
      {
        if (threadToKill != null)
        {
          try { threadToKill.Abort(); }
          catch { /* Ignore */ }
        }

        if (abort != null)
          abort();

        throw new TimeoutException();
      }
    }
  }
}

Just run this in a loop with appropriate timeout control.

DateTime endAt = DateTime.Now.AddMinutes(1);
Timespan timeout = new Timespan( 0, 0, 0, 5);
while( DateTime.Now < endAt )
{
    try
    {
        TimeoutWrapper.Invoke( timeout, () => DoSomething());
        break;
    } 
    catch( TimeoutException ex ) 
    { /* Do something */ }
}

Upvotes: 1

Samuel
Samuel

Reputation: 38346

It's not the prettiest thing, but I seems to work nicely so far. And it doesn't use exceptions to indicate a timeout.

public static class TimeoutOperation
{
  private static readonly TimeSpan DefaultTimeout = new TimeSpan(0, 0, 10);
  private static readonly TimeSpan DefaultGranularity = new TimeSpan(0, 0, 0, 0, 100);

  public static ThreadResult<TResult> DoWithTimeout<TResult>(Func<TResult> action)
  {
    return DoWithTimeout<TResult>(action, DefaultTimeout);
  }

  public static ThreadResult<TResult> DoWithTimeout<TResult>(Func<TResult> action, TimeSpan timeout)
  {
    return DoWithTimeout<TResult>(action, timeout, DefaultGranularity);
  }

  public static ThreadResult<TResult> DoWithTimeout<TResult>(Func<TResult> action, TimeSpan timeout, TimeSpan granularity)
  {
    Thread thread = BuildThread<TResult>(action);
    Stopwatch stopwatch = Stopwatch.StartNew();
    ThreadResult<TResult> result = new ThreadResult<TResult>();

    thread.Start(result);
    do
    {
      if (thread.Join(granularity) && !result.WasSuccessful)
      {
        thread = BuildThread<TResult>(action);
        thread.Start(result);
      }

    } while (stopwatch.Elapsed < timeout && !result.WasSuccessful);
    stopwatch.Stop();

    if (thread.ThreadState == System.Threading.ThreadState.Running)
      thread.Abort();

    return result;
  }

  private static Thread BuildThread<TResult>(Func<TResult> action)
  {
    return new Thread(p =>
    {
      ThreadResult<TResult> r = p as ThreadResult<TResult>;
      try { r.Result = action(); r.WasSuccessful = true; }
      catch (Exception) { r.WasSuccessful = false; }
    });
  }

  public class ThreadResult<TResult>
  {
    public TResult Result { get; set; }
    public bool WasSuccessful { get; set; }
  }
}
Usage
var result = TimeoutOperation.DoWithTimeout<int>(() =>
  {
    Thread.Sleep(100);
    throw new Exception();
  });
result.WasSuccessful // = false
result.Value // = 0

var result = TimeoutOperation.DoWithTimeout<int>(() =>
  {
    Thread.Sleep(2000);
    return 5;
  });
result.WasSuccessful // = true
result.Value // = 5

Upvotes: 1

Andriy Volkov
Andriy Volkov

Reputation: 18923

Create a method that takes a lambda expression for Statement To Execute and a parameter for timeout. Inside that method execute the lambda expression inside the try / catch block and use parameter for the timeout.

Upvotes: 0

Related Questions