Task.Delay fails if you wait longer than 49.7 days. So something like: await Task.Delay(TimeSpan.FromDays(50)); will fail. But why and should you care?
The Why
If you try to execute this code:
using System;
using System.Threading.Tasks;
await Task.Delay(TimeSpan.FromDays(50));
If you want to try: sharplab.io
You will be greeted by:
System.ArgumentOutOfRangeException: The value needs to translate in milliseconds to -1 (signifying an infinite timeout), 0, or a positive integer less than or equal to the maximum allowed timer duration. (Parameter 'delay')
Using TimeSpan.FromDays(49) works.
Hmm, but don't we have a positive amount? Well yes, but internally Task.Delay will use uint as its base unit. Therefore the max value is uint.MaxValue - 1 which is "4294967294U". That is in milliseconds, so: 4294967294 ms / 1000 (to seconds) / 60 (to minutes) / 60 (to hours) / 24 (to days) = 49.7103 Days.
Task.Delay internally also checks that:
internal static uint ValidateTimeout(TimeSpan timeout, ExceptionArgument argument)
{
long totalMilliseconds = (long) timeout.TotalMilliseconds;
if (totalMilliseconds < -1L || totalMilliseconds > 4294967294L)
ThrowHelper.ThrowArgumentOutOfRangeException(argument, ExceptionResource.Task_InvalidTimerTimeSpan);
return (uint) totalMilliseconds;
}
This method is called when you call Task.Delay.
Should I care?
Probably: Not. Only if you write some background logic that has to delay work far far in the future. I did come across that, when I wrote my own Job-Scheduler: NCronJob. Basically, the user can enter any CRON notation and it should work. So if you want to have a job executed only once a year, of course that has to work. So what can we do? Well, I wrote a helper for that:
/// <summary>
/// Represents extensions for the <see cref="Task"/> object.
/// </summary>
internal static class TaskExtensions
{
/// <summary>
/// Has the same semantics as <see cref="Task.Delay(TimeSpan, TimeProvider, CancellationToken)"/> but is safe to use for long delays.
/// </summary>
/// <param name="targetTimeSpan">The <see cref="TimeSpan" /> to wait before completing the returned task, or <see cref="Timeout.InfiniteTimeSpan" /> to wait indefinitely.</param>
/// <param name="timeProvider">The <see cref="TimeProvider" /> with which to interpret <paramref name="targetTimeSpan" />.</param>
/// <param name="token">A cancellation token to observe while waiting for the task to complete.</param>
public static async Task LongDelaySafe(TimeSpan targetTimeSpan, TimeProvider timeProvider, CancellationToken token)
{
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(targetTimeSpan, TimeSpan.Zero);
var remainingTime = targetTimeSpan;
while (remainingTime > TimeSpan.Zero)
{
var delay = TimeSpan.FromMilliseconds(Math.Min(remainingTime.TotalMilliseconds, uint.MaxValue - 1));
await Task.Delay(delay, timeProvider, token).ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
remainingTime -= delay;
}
}
}
So basically, we divide and conquer at the uint.MaxValue boundary and await those multiple times until we are at the specific time given by the user.

