Task.Delay 超过 int.MaxValue 毫秒

Task.Delay for more than int.MaxValue milliseconds

可以告诉 Task.Delay 延迟的最大持续时间是 int.MaxValue 毫秒。创建一个将延迟超过该时间的 Task 的最干净的方法是什么?

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

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

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

也是如此

您最多只能等待两次:

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

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

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

您可以延迟多次。例如:

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() 方法真的是您场景的最佳解决方案吗?详细了解您为什么要等待这么长时间可能会帮助其他人为您提供更好的解决实际问题的方法,而不是解决这个非常具体的需求。

您无法使用单个 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;
    }
}

如果您关心精度,您应该使用 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));
        }
    }

从 .NET 6 开始,Task.Delay method is now 4,294,967,294 milliseconds (0xFFFFFFFE, or UInt32.MaxValue - 1), which is approximately 49 days and 17 hours. It is the same limit with the dueTime/period arguments of the System.Threading.Timer 构造函数的最大有效 TimeSpan delay 值,Task.Delay 在内部基于该值。

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