---
title: "Task.Delay fails if you wait longer than 49.7 days"
description: "`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?"
updated: 2025-12-28
tags: ["C#", ".NET"]
canonical: "https://steven-giesel.com/blogPost/c3bdad0d-f9eb-4dcf-b650-98ec0f5dd4d4/taskdelay-fails-if-you-wait-longer-than-497-days"
---

# Task.Delay fails if you wait longer than 49.7 days

`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:
```csharp
using System;
using System.Threading.Tasks;

await Task.Delay(TimeSpan.FromDays(50));
```
<sup>If you want to try: [sharplab.io](https://sharplab.io/#v2:C4LgTgrgdgPgAgJgIwFgBQcAMACOSCsA3OunAJy4BsAdACICmANgIYCeAFACoCWAtvQGUADsyjUAYmAD2vWmwDO7fJgCUKwkA===)</sup>

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

<sup>Using `TimeSpan.FromDays(49)` works.</sup>

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](https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs,5783) also checks that:

```csharp
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**](https://github.com/NCronJob-Dev/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](https://github.com/NCronJob-Dev/NCronJob/blob/45ce3f017938f2ddf20ab955389d48f3e701096b/src/NCronJob/Scheduler/TaskExtensions.cs#L6):
```csharp
/// <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;
        }
    }
}
```

Or if you want to use the C# 14 extension syntax so you can call: `Task.LongDelaySafe`:
```csharp
internal static class TaskExtensions
{
    extension(Task)
    {
        /// <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.