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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/715647/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 22:14:29  来源:igfitidea点击:

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

c#.net

提问by Mike

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

我正在寻找一种通用的尝试,并在 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;
        }
    }
}

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.

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

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?

澄清:有关声明可能会是这样的一个任务。如果我使用委托和 method.Invoke,调用范围不是委托内部的,而不是原始方法?

采纳答案by strager

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 DoOrTimeoutwith a delegate as the first parameter.

只需DoOrTimeout将委托作为第一个参数调用即可。

回答by zvolkov

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.

创建一个方法,该方法采用 Statement To Execute 的 lambda 表达式和超时参数。在该方法中,执行 try / catch 块中的 lambda 表达式并使用超时参数。

回答by Samuel

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

回答by chilltemp

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

看看这个问题。你所要求的正是我想要的用途之一。
实现 C# 通用超时

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

警告:此示例使用 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();
      }
    }
  }
}

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

回答by Alex

This code is wrong (infinite loop):

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

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

The right one is:

正确的是:

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

回答by fijiaaron

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