.net 在C#中使用超时的通用尝试和重试?

4ktjp1zp  于 2023-04-07  发布在  .NET
关注(0)|答案(7)|浏览(119)

我正在寻找一个通用的尝试和重试与超时在C#.基本上,我想要以下:

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

在上面的例子中,我等待了10秒钟,但它应该是一个基于参数的变量timeout。我不想在需要使用它的地方重复完整的代码。在我的代码中有很多地方没有内置到API中的超时,如果应用程序没有准备好执行语句,我会遇到异常。这也将避免在这些满足之前在我的应用程序中硬编码延迟。

**说明:**这里的语句可能是赋值语句,如果我使用委托和方法.Invoke,调用的作用域不是在委托内部,而不是在原始方法内部吗?

gv8xihay

gv8xihay1#

使用您的示例,解决方案很简单:

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

只需调用DoOrTimeout,并将delegate作为第一个参数。

mrwjdhj3

mrwjdhj32#

这不是最漂亮的东西,但到目前为止我似乎工作得很好,而且它没有使用异常来指示超时。

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

用法

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
ssgvzors

ssgvzors3#

看一下这个问题,你的要求正是我打算的用途之一。
Implement C# Generic Timeout
警告:此示例使用Thread.Abort。请点击我最初问题的链接,阅读评论中关于此问题的一些警告。

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

只需在一个循环中运行此代码,并使用适当的超时控制。

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 */ }
}
abithluo

abithluo4#

创建一个方法,该方法接受一个lambda表达式作为Statement To Execute,接受一个参数作为timeout。在该方法中,在try / catch块中执行lambda表达式,并使用参数作为timeout。

p8h8hvxi

p8h8hvxi5#

这里有一个简单的解决方案:

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

klh5stk16#

这段代码是错误的(无限循环):

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

正确的是:

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

wlwcrazw7#

这是我使用的解决方案,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;
            }
        }
    }
}

示例用法:

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

    ...
}

相关问题