【问题标题】:Task.Delay for more than int.MaxValue millisecondsTask.Delay 超过 int.MaxValue 毫秒
【发布时间】:2022-05-04 05:28:05
【问题描述】:

Task.Delay 可以被告知延迟的最大持续时间是int.MaxValue 毫秒。创建Task 的最简洁方法是什么?

// Fine.
await Task.Delay(TimeSpan.FromMilliseconds(int.MaxValue));

// ArgumentOutOfRangeException
await Task.Delay(TimeSpan.FromMilliseconds(int.MaxValue + 1L));

【问题讨论】:

  • 我无法理解在什么情况下您会想要延迟……(支票计算器)……24.86 天。
  • 25 天的等待时间很长。如果您想永远等待,可以使用Task.Delay(-1)
  • @DanielMann 我正在编写一个缓存,它会在给定时间段后删除条目。虽然不太可能有人想要缓存这么长时间的东西,但我不希望它成为一个限制因素。
  • Task.Delay 似乎不适合这种情况。
  • 您有其他方法吗?我的代码(简化)归结为:AddEntry(key, value); Task.Delay(12345).ContinueWith(/* Remove entry. */);

标签: c# .net async-await task task-parallel-library


【解决方案1】:

您无法使用单个 Task.Delay 实现此目的,因为它在内部使用仅接受 int 的 System.Threading.Timer

但是,您可以使用多个等待一个接一个地执行此操作。这是最干净的方法:

static async Task Delay(long delay)
{
    while (delay > 0)
    {
        var currentDelay = delay > int.MaxValue ? int.MaxValue : (int) delay;
        await Task.Delay(currentDelay);
        delay -= currentDelay;
    }
}

【讨论】:

    【解决方案2】:

    您可以轻松编写一个方法将其分解为更小的延迟:

    private static readonly TimeSpan FullDelay = TimeSpan.FromMilliseconds(int.MaxValue);
    
    private static async Task LongDelay(TimeSpan delay)
    {
        long fullDelays = delay.Ticks / FullDelay.Ticks;
        TimeSpan remaining = delay;
        for(int i = 0; i < fullDelays; i++)
        {
            await Task.Delay(FullDelay);
            remaining -= FullDelay;
        }
    
        await Task.Delay(remaining); 
    }
    

    【讨论】:

      【解决方案3】:

      您可以延迟多次。例如:

      static async Task LongDelay(long milliseconds)
      {
          if (milliseconds < 0)
          {
              throw new ArgumentOutOfRangeException();
          }
      
          if (milliseconds == 0)
          {
              return;
          }
      
          int iterations = (milliseconds - 1) / int.MaxValue;
      
          while (iterations-- > 0)
          {
              await Task.Delay(int.MaxValue);
              milliseconds -= int.MaxValue;
          }
      
          await Task.Delay(milliseconds);
      }
      

      也就是说,int.MaxValue 毫秒是一个真的很长的时间,差不多 25 天!恕我直言,一个更重要的问题是,Task.Delay() 方法真的是您场景的最佳解决方案吗?更多地了解您为什么要等待这么长时间可能会帮助其他人为您提供更好的实际问题解决方案,而不是解决这个非常具体的需求。

      【讨论】:

        【解决方案4】:

        如果您关心精度,您应该使用Stopwatch 而不是将delay 划分为Int16.MaxValue 块。这就是以下代码与其他答案的不同之处:

            private static async Task LongDelay(TimeSpan delay)
            {
                var st = new System.Diagnostics.Stopwatch();
                st.Start();
                while (true)
                {
                    var remaining = (delay - st.Elapsed).TotalMilliseconds;
                    if (remaining <= 0)
                        break;
                    if (remaining > Int16.MaxValue)
                        remaining = Int16.MaxValue;
                    await Task.Delay(TimeSpan.FromMilliseconds(remaining));
                }
            }
        

        更新:根据@CoryNelson 的评论,Stopwatch 对于长圈来说不够好。如果是这样,可以简单地使用DateTime.UtcNow

            private static async Task LongDelay(TimeSpan delay)
            {
                var start = DateTime.UtcNow;
                while (true)
                {
                    var remaining = (delay - (DateTime.UtcNow - start)).TotalMilliseconds;
                    if (remaining <= 0)
                        break;
                    if (remaining > Int16.MaxValue)
                        remaining = Int16.MaxValue;
                    await Task.Delay(TimeSpan.FromMilliseconds(remaining));
                }
            }
        

        【讨论】:

        • 由于不受系统日期/时间(及其调整)的影响,秒表会随着系统时钟的不准确性而出现一定程度的偏差。它不适用于长时间测量。
        • @CoryNelson,有趣,我不知道Stopwatch 的这些缺点。你能指出一些证实这一点的消息来源吗?无论如何,我相信使用DateTime.UtcNow 应该足够可靠,我更新了代码。
        • 查看QueryPerformanceCounter 文档,不容易链接,但要搜索“累积错误”。这是 Stopwatch 在幕后使用的。
        【解决方案5】:

        从 .NET 6 开始,Task.Delay 方法的最大有效 TimeSpan delay 值现在为 4,294,967,294 毫秒(0xFFFFFFFEUInt32.MaxValue - 1),大约为 49 天 17 小时。这与 System.Threading.Timer 构造函数的 dueTime/period 参数的限制相同,Task.Delay 内部基于该参数。

        相关 GitHub 问题:Task.Delay actual accepted delay is twice the documented value

        【讨论】:

          【解决方案6】:

          你不能。如果您传递的值将解析为比 Int32.MaxValue 更大的毫秒数,则每个重载都会引发 ArgumentOutOfRange 异常。即使TimeSpan 过载 (MSDN) 也是如此。

          你能做的最好的就是等待两次:

          await Task.Delay(TimeSpan.FromMilliseconds(int.MaxValue));
          await Task.Delay(TimeSpan.FromMilliseconds(20));
          

          【讨论】:

          • 这也会引发ArgumentOutOfRangeException
          猜你喜欢
          • 2020-04-06
          • 1970-01-01
          • 2021-12-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-15
          • 2023-03-04
          • 2022-01-25
          相关资源
          最近更新 更多