General purpose Try and Retry with a Timeout in C#

2020-06-04 04:51发布

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?

标签: c# .net
6条回答
你好瞎i
2楼-- · 2020-06-04 05:31

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");
    }
}
查看更多
放荡不羁爱自由
3楼-- · 2020-06-04 05:32

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.

查看更多
女痞
4楼-- · 2020-06-04 05:41

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
查看更多
Bombasti
5楼-- · 2020-06-04 05:45

This code is wrong (infinite loop):

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

The right one is:

if (DateTime.Now.Subtract(time).TotalMilliseconds > 10000)
查看更多
够拽才男人
6楼-- · 2020-06-04 05:50

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.

查看更多
beautiful°
7楼-- · 2020-06-04 05:56

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 */ }
}
查看更多
登录 后发表回答