【问题标题】:Implementing a timeout in c#在 C# 中实现超时
【发布时间】:2012-04-26 00:05:06
【问题描述】:

我是 C# 新手;我主要做Java。

我想按照以下方式实现超时:

int now= Time.now();
while(true)
{
  tryMethod();
  if(now > now+5000) throw new TimeoutException();
}

如何在 C# 中实现这一点?谢谢!

【问题讨论】:

  • 你会想看看Stopwatch类。 Here 就是一个例子
  • tryMethod 做了什么?在上面的代码中,您的 if 语句只有在 trymethod 完成后才能到达。
  • 这似乎是您可能想要异步执行的事情?
  • tryMethod() 是同步还是异步?
  • 您可以使用Timer 类在 5 秒内运行一些代码。显然,您需要中止任何其他线程或一遍又一遍地调用 tryMethod 的东西;只是在timer.Tick 事件中抛出异常不会满足您的需要。

标签: c# timeout


【解决方案1】:

一种可能的方法是:

Stopwatch sw = new Stopwatch();
sw.Start();

while(true)
{
    tryMethod();
    if(sw.ElapsedMilliseconds > 5000) throw new TimeoutException();
}

但是,您目前无法跳出循环。我建议让tryMethod 返回bool 并将其更改为:

Stopwatch sw = new Stopwatch();
sw.Start();

while(!tryMethod())
{
    if(sw.ElapsedMilliseconds > 5000) throw new TimeoutException();
}

【讨论】:

  • 在夏令时遇到问题
  • @Servy 你是对的,编辑我的答案以使用秒表而不是 DateTime。
  • 我很确定 OP 希望能够在 tryMethod 上执行超时操作
  • @AdamKing 我不同意,因为这不是原始 Java 代码所做的,它只是重复调用 tryMethod() 5000 毫秒。
  • @AdamKing 那为什么它在一个循环中,而不是一次?
【解决方案2】:

我认为您可以使用计时器和委托来执行此操作,我的示例代码如下:

using System;
using System.Timers;

class Program
{
    public delegate void tm();

    static void Main(string[] args)
    {
        var t = new tm(tryMethod);
        var timer = new Timer();
        timer.Interval = 5000;

        timer.Start();

        timer.Elapsed += (sender, e) => timer_Elapsed(t);
        t.BeginInvoke(null, null);
    }

    static void timer_Elapsed(tm p)
    {
        p.EndInvoke(null);
        throw new TimeoutException();
    }

    static void tryMethod()
    {
        Console.WriteLine("FooBar");
    }
}

你有 tryMethod,然后你创建一个委托并将这个委托指向 tryMethod,然后你异步启动这个委托。然后你有一个计时器,间隔为 5000 毫秒,你将你的委托传递给你的计时器经过的方法(它应该作为一个委托是一个引用类型,而不是一个值类型),一旦 5000 秒过去了,你调用 EndInvoke您的委托上的方法。

【讨论】:

  • 我认为这与 OP 想要的相反 - 这段代码确保方法不会在至少 5000 毫秒内完成,看起来 OP 想要重复运行一个方法最多 5000 毫秒。
  • 啊抱歉,我搞错了!
  • 另一个答案(while 循环版本)似乎不适用于阻塞操作。我会做一些测试,但现在只是问一下,这种计时器方法是否适用于阻塞?
  • @liang 你在问我两年前做过的事情!我不是 100% 确定,但我认为有些定时器是同步的,而另一些是异步的,你需要使用正确的。
【解决方案3】:

这个问题已经很老了,但还有另一种选择。

using(CancellationTokenSource cts = new CancellationTokenSource(5000))
{
  cts.Token.Register(() => { throw new TimeoutException(); });
  while(!cts.IsCancellationRequested)
  {
    tryMethod();
  }
}

从技术上讲,您还应该在 tryMethod() 中传播 CancellationToken 以优雅地中断它。

工作演示:(注意我必须删除异常抛出行为,因为 .netfiddle 不喜欢它。)

https://dotnetfiddle.net/WjRxyk

【讨论】:

    【解决方案4】:

    只要 tryMethod() 不阻止它应该做你想做的事:

    在夏令时或移动时更改时区不安全:

    DateTime startTime = DateTime.Now;
    
    while(true)
    {
        tryMethod();
        if(DateTime.Now.Subtract(startTime).TotalMilliseconds > 5000)
            throw new TimeoutException();
    }
    

    时区和夏令时安全版本:

    DateTime startTime = DateTime.UtcNow;
    
    while(true)
    {
        tryMethod();
        if(DateTime.UtcNow.Subtract(startTime).TotalMilliseconds > 5000)
            throw new TimeoutException();
    } 
    

    (DateTimeOffset 需要 .NET 3.5 或更高版本。)

    DateTimeOffset startTime = DateTimeOffset.Now;
    
    while(true)
    {
        tryMethod();
        if(DateTimeOffset.Now.Subtract(startTime).TotalMilliseconds > 5000)
            throw new TimeoutException();
    } 
    

    【讨论】:

    • 最好将 DateTime 转换为世界时间以便考虑不同的时区?另外,如果我使用 UtcNow 会有什么不同吗?
    • @SimpleFellow 有两种方法可以使这个时区和夏令时安全。请参阅更新的答案。使用 UtcNow 比转换要好,因为根据执行转换的时间,转换仍然可能存在问题。
    【解决方案5】:

    在异步方法上使用任务自定义超时

    这是我的自定义类的实现,该类具有包装任务以实现超时的方法。

    public class TaskWithTimeoutWrapper
    {
        protected volatile bool taskFinished = false;
    
        public async Task<T> RunWithCustomTimeoutAsync<T>(int millisecondsToTimeout, Func<Task<T>> taskFunc, CancellationTokenSource cancellationTokenSource = null)
        {
            this.taskFinished = false;
    
            var results = await Task.WhenAll<T>(new List<Task<T>>
            {
                this.RunTaskFuncWrappedAsync<T>(taskFunc),
                this.DelayToTimeoutAsync<T>(millisecondsToTimeout, cancellationTokenSource)
            });
    
            return results[0];
        }
    
        public async Task RunWithCustomTimeoutAsync(int millisecondsToTimeout, Func<Task> taskFunc, CancellationTokenSource cancellationTokenSource = null)
        {
            this.taskFinished = false;
    
            await Task.WhenAll(new List<Task>
            {
                this.RunTaskFuncWrappedAsync(taskFunc),
                this.DelayToTimeoutAsync(millisecondsToTimeout, cancellationTokenSource)
            });
        }
    
        protected async Task DelayToTimeoutAsync(int millisecondsToTimeout, CancellationTokenSource cancellationTokenSource)
        {
            await Task.Delay(millisecondsToTimeout);
    
            this.ActionOnTimeout(cancellationTokenSource);
        }
    
        protected async Task<T> DelayToTimeoutAsync<T>(int millisecondsToTimeout, CancellationTokenSource cancellationTokenSource)
        {
            await this.DelayToTimeoutAsync(millisecondsToTimeout, cancellationTokenSource);
    
            return default(T);
        }
    
        protected virtual void ActionOnTimeout(CancellationTokenSource cancellationTokenSource)
        {
            if (!this.taskFinished)
            {
                cancellationTokenSource?.Cancel();
                throw new NoInternetException();
            }
        }
    
        protected async Task RunTaskFuncWrappedAsync(Func<Task> taskFunc)
        {
            await taskFunc.Invoke();
    
            this.taskFinished = true;
        }
    
        protected async Task<T> RunTaskFuncWrappedAsync<T>(Func<Task<T>> taskFunc)
        {
            var result = await taskFunc.Invoke();
    
            this.taskFinished = true;
    
            return result;
        }
    }
    

    那么你可以这样称呼它:

    await new TaskWithTimeoutWrapper().RunWithCustomTimeoutAsync(10000, () => this.MyTask());
    

    var myResult = await new TaskWithTimeoutWrapper().RunWithCustomTimeoutAsync(10000, () => this.MyTaskThatReturnsMyResult());
    

    如果你想在超时时取消正在运行的异步任务,你可以添加一个取消令牌。

    希望对你有帮助

    【讨论】:

      【解决方案6】:

      我喜欢的另一种方式:

      public class TimeoutAction
          {
              private Thread ActionThread { get; set; }
              private Thread TimeoutThread { get; set; }
              private AutoResetEvent ThreadSynchronizer { get; set; }
              private bool _success;
              private bool _timout;
      
              /// <summary>
              /// 
              /// </summary>
              /// <param name="waitLimit">in ms</param>
              /// <param name="action">delegate action</param>
              public TimeoutAction(int waitLimit, Action action)
              {
                  ThreadSynchronizer = new AutoResetEvent(false);
                  ActionThread = new Thread(new ThreadStart(delegate
                  {
                      action.Invoke();
                      if (_timout) return;
                      _timout = true;
                      _success = true;
                      ThreadSynchronizer.Set();
                  }));
      
                  TimeoutThread = new Thread(new ThreadStart(delegate
                  {
                      Thread.Sleep(waitLimit);
                      if (_success) return;
                      _timout = true;
                      _success = false;
                      ThreadSynchronizer.Set();
                  }));
              }
      
              /// <summary>
              /// If the action takes longer than the wait limit, this will throw a TimeoutException
              /// </summary>
              public void Start()
              {
                  ActionThread.Start();
                  TimeoutThread.Start();
      
                  ThreadSynchronizer.WaitOne();
      
                  if (!_success)
                  {
                      throw new TimeoutException();
                  }
                  ThreadSynchronizer.Close();
              }
          }
      

      【讨论】:

        【解决方案7】:
        CancellationTokenSource cts = new CancellationTokenSource();
        cts.CancelAfter(10000);
        try
        {
            Task task = Task.Run(() => { methodToTimeoutAfter10Seconds(); }, cts.Token);
            TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
            using (cts.Token.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
            {
                if (task != await Task.WhenAny(task, tcs.Task))
                {
                    throw new OperationCanceledException(cts.Token);
                }
            }
            /* Wait until the task is finish or timeout. */
            task.Wait();
        
            /* Rest of the code goes here */
            
        }
        catch (TaskCanceledException)
        {
            Console.WriteLine("Timeout");
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Timeout");
        }
        catch (Exception ex)
        {
            Console.WriteLine("Other exceptions");
        }
        finally
        {
            cts.Dispose();
        }   
        

        【讨论】:

        • 欢迎来到 StackOverflow。请修改您的帖子并提供一些解释,而不仅仅是代码。
        猜你喜欢
        • 2011-06-28
        • 2013-05-06
        • 2023-03-27
        • 2017-03-25
        • 2011-08-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多