'Task.Delay for more than int.MaxValue milliseconds

The maximum duration a Task.Delay can be told to delay is int.MaxValue milliseconds. What is the cleanest way to create a Task which will delay beyond that time?

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

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


Solution 1:[1]

You can easily write a method to break it down into smaller delays:

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

Solution 2:[2]

You can delay multiple times. For example:

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

That said, int.MaxValue milliseconds is a really long time, almost 25 days! IMHO a much more important question is, is the Task.Delay() method really the best solution for your scenario? Knowing more about why you are trying to wait for such a long period of time might help others offer you a better solution to the actual problem, instead of addressing this very specific need.

Solution 3:[3]

If you care about precision, you should be using Stopwatch rather than deviding delay by Int16.MaxValue chunks. This is how the below code is different from other answers:

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

UPDATE: According to @CoryNelson's comment, Stopwatch is not good enough for long laps. If so, it's possible to simply use 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));
        }
    }

Solution 4:[4]

As of .NET 6, the maximum valid TimeSpan delay value of the 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 constructor, on which the Task.Delay is based internally.

Related GitHub issue: Task.Delay actual accepted delay is twice the documented value

Solution 5:[5]

You cannot. Every overload will throw an ArgumentOutOfRange exception if you pass a value that would resolve to a greater number of milliseconds than Int32.MaxValue. This is true even for the TimeSpan overload (MSDN).

The best you could do is await twice:

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mike Zboray
Solution 2 Peter Duniho
Solution 3
Solution 4 Theodor Zoulias
Solution 5