【问题标题】:General purpose Try and Retry with a Timeout in C#?在 C# 中使用超时的通用尝试和重试?
【发布时间】:2010-10-17 11:16:48
【问题描述】:

我正在寻找通用的尝试并在 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 秒,但它应该是基于参数的可变超时。我不想在需要使用它的地方重复这个完整的代码。我的代码中有多个地方不是 API 内置的超时,如果应用程序还没有准备好执行语句,我会遇到异常。这也可以避免在这些声明之前对我的应用程序中的延迟进行硬编码。

澄清:有问题的陈述可能类似于作业。如果我使用委托和method.Invoke,调用范围不是在委托内部而不是原始方法吗?

【问题讨论】:

    标签: c# .net


    【解决方案1】:

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

    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,并将委托作为第一个参数。

    【讨论】:

    • 请使用throw; 重新抛出异常,而不是throw ex;,因为后者会破坏堆栈跟踪。
    • @Will,我使用了 OP 的代码。不过,你是对的; throw 应该单独使用。我会更新我的答案以反映这一点。
    • 你错过了 stopTrying = true;调用“method.Invoke()”后;
    • 此代码无法编译。委托不能用作约束。
    • 比较一个TimeSpan 上的Milliseconds 和另一个上的TotalMilliseconds 肯定是错字(错误)。为什么不直接比较TimeSpan?该类型重载了通常的运算符。因此:if (DateTime.Now - time &gt; timeout) { ... } 更正确。
    【解决方案2】:

    创建一个方法,该方法接受一个用于执行语句的 lambda 表达式和一个用于超时的参数。在该方法中执行 try / catch 块中的 lambda 表达式并使用超时参数。

    【讨论】:

      【解决方案3】:

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

      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
      

      【讨论】:

        【解决方案4】:

        看看这个问题。您要求的正是我想要的用途之一。
        Implement C# Generic Timeout

        警告:此示例使用 Thread.Abort。按照我原来的问题的链接阅读 cmets 中的一些警告。

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

        【讨论】:

          【解决方案5】:

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

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

          正确的是:

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

          【讨论】:

            【解决方案6】:

            这是一个简单的解决方案:

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

            【讨论】:

            • 如果您不想处理它,也可以直接“中断”而不是抛出异常。
            猜你喜欢
            • 2023-03-03
            • 1970-01-01
            • 2011-12-02
            • 1970-01-01
            • 1970-01-01
            • 2013-11-12
            • 1970-01-01
            • 2015-08-10
            • 1970-01-01
            相关资源
            最近更新 更多