'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 |
