Task.Delay for more than int.MaxValue milliseconds - c#

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

You can't achieve that using a single Task.Delay since it internally uses a System.Threading.Timer that only accepts an int.
However you can do that using multiple waits one after the other. Here's the cleanest way:
static async Task Delay(long delay)
{
while (delay > 0)
{
var currentDelay = delay > int.MaxValue ? int.MaxValue : (int) delay;
await Task.Delay(currentDelay);
delay -= currentDelay;
}
}

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

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.

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

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

Improved on #i3arnon version, which use multiple waits (delays) one after the other.:
Added support for optional cancellation token.
Added support for TimeSpan argument.
The method reproduces the same behavior as the original Task.Delay.
Parameter names are more consistent with the original method.
Added usage remark.
Note: Using the scheduler would be a good alternative to implement long delays.
/// <summary>Allow to delay Task for 292,471,209 years.</summary>
/// <remarks>Usage makes sense if the process won't be recycled before the delay expires.</remarks>
public static async Task LongDelay(
TimeSpan delay,
CancellationToken cancellationToken = default(CancellationToken)
) => await LongDelay((long)delay.TotalMilliseconds, cancellationToken).ConfigureAwait(false);
/// <summary>Allow to delay Task for 292,471,209 years.</summary>
/// <remarks>Usage makes sense if the process won't be recycled before the delay expires.</remarks>
public static async Task LongDelay(
long millisecondsDelay,
CancellationToken cancellationToken = default(CancellationToken)
) {
// Use 'do' to run Task.Delay at least once to reproduce the same behavior.
do {
var delay = (int)Math.Min(int.MaxValue, millisecondsDelay);
await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
millisecondsDelay -= delay;
} while (millisecondsDelay > 0);
}
LongDelay example with CancellationToken:
// Create a token that auto-cancels after 10 seconds.
var source = new CancellationTokenSource(10000);
// Delay for 20 seconds.
try { LongDelay(20000, source.Token).Wait(); }
catch (TaskCanceledException) { } // Cancel silently.
catch (Exception) { throw; }
Edit: Applied suggestion by #TheodorZoulias

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

Related

async Task Wait behavior in static constructor waits for entire time even though task has finished

When calling Task.Run(..)..Wait(...) in a static constructor it waits for the entire timeout time even though the task has finished. Just curious why this is? What is best practice for this scenario? Test class that shows it below
static TestClass()
{
var delay = 100;
var wait = 500;
// Will wait the whole wait time
var sw = Stopwatch.StartNew();
Task.Run(() => DoStuff(delay)).Wait(wait);
sw.Stop();
var elapsedMs = sw.ElapsedMilliseconds;
Debug.WriteLine($"Elapsed {elapsedMs}");
// Will return when task is complete
sw.Restart();
Task.Run(()=>
{
var awaiter = Task.Run(() => DoStuff(delay)).GetAwaiter();
var maxMs = delay * TimeSpan.TicksPerMillisecond;
var swElapse = Stopwatch.StartNew();
while (!awaiter.IsCompleted && swElapse.ElapsedTicks < maxMs)
{ }
swElapse.Stop();
}).Wait(wait);
sw.Stop();
elapsedMs = sw.ElapsedMilliseconds;
Debug.WriteLine($"Elapsed {elapsedMs}");
}
static void DoStuff(int delay)
{
// Some async task called and waited for result
Task.Run(async () => await Task.Delay(100)).GetAwaiter().GetResult();
}
}
in a static constructor
Last time I checked, static constructors take a lock because only one of them can execute at a time. So, if the static constructor queues work to another thread that then does other things (i.e., call other static constructors) while the original static constructor is blocked on that other thread, then you can end up with a deadlock that is only resolved with the timeout.
What is best practice for this scenario?
Don't block on async code, especially not in constructors, and especially especially not in static constructors. (Link is to my blog).
var delay = 100;
var maxMs = delay * TimeSpan.TicksPerMillisecond;
var secondsTotal = maxMs / 1000.0m;
Console.WriteLine("Total seconds thread will wait: " + secondsTotal + "s.");
When you run this code, you get: 1000s. 1000 seconds is 16.7 minutes.
Your while condition is:
!awaiter.IsCompleted && swElapse.ElapsedTicks < maxMs
Because this is the "AND" operation, the while loop will continue so long as what's in the parentheses is true.
In your case the "awaiter.IsCompleted" evaluates for true, AND less than 16 minutes evaluates for true as well (until 16 minutes pass). Only until one of the conditions is false will the while loop stop repeating.
This is likely why you are experiencing this behavior.

Get result of Func<Task<IResponse>> inside timeout check

I'm kinda new at async programming so don't uderstand many things. Please, I need a little bit more help. I did as was recommened in my other question.
public async Task<TResponse> SendRequestAsync<TResponse>(Func<Task<TResponse>> sendAsync)
{
int timeout = 15;
if (await Task.WhenAny(sendAsync, Task.Delay(timeout) == sendAsync))
{
return await sendAsync();
}
else
{
throw new Exception("time out!!!");
}
}
But I need to get a result of sendAsync() and return it. So have I questions:
1) What the best way to do that and how to use Task.Delay with Func<Task<TResponse>>(or may be something instead of it)? I can't figure out how convert(or something) Func to Task.
2) It seems that return await sendAsync() inside if permorms request once more. It is not great. Can I get result of my Func<Task<..>> inside if somehow?
Since you are new in async programming - it's better to not put too much stuff in one statement and better split that:
public async Task<TResponse> SendRequestAsync<TResponse>(Func<Task<TResponse>> sendAsync) {
int timeout = 15;
// here you create Task which represents ongoing request
var sendTask = sendAsync();
// Task which will complete after specified amount of time, in milliseconds
// which means your timeout should be 15000 (for 15 seconds), not 15
var delay = Task.Delay(timeout);
// wait for any of those tasks to complete, returns task that completed first
var taskThatCompletedFirst = await Task.WhenAny(sendTask, delay);
if (taskThatCompletedFirst == sendTask) {
// if that's our task and not "delay" task - we are fine
// await it so that all exceptions if any are thrown here
// this will _not_ cause it to execute once again
return await sendTask;
}
else {
// "delay" task completed first, which means 15 seconds has passed
// but our request has not been completed
throw new Exception("time out!!!");
}
}
Request is sent twice because sendAsync is a Func returning Task, different on each call. You call it first under Task.WhenAny() and repeat in operator return await sendAsync().
To avoid this duplicated call you should save a task to variable and pass that task to both calls:
public async Task<TResponse> SendRequestAsync<TResponse>(Func<Task<TResponse>> sendAsync)
{
int timeout = 15;
var task = sendAsync();
if (await Task.WhenAny(task, Task.Delay(timeout) == task))
{
return await task;
}
else
{
throw new Exception("time out!!!");
}
}
await on completed task will just return its result without rerunning the task.

Simple way to rate limit HttpClient requests

I am using the HTTPClient in System.Net.Http to make requests against an API. The API is limited to 10 requests per second.
My code is roughly like so:
List<Task> tasks = new List<Task>();
items..Select(i => tasks.Add(ProcessItem(i));
try
{
await Task.WhenAll(taskList.ToArray());
}
catch (Exception ex)
{
}
The ProcessItem method does a few things but always calls the API using the following:
await SendRequestAsync(..blah). Which looks like:
private async Task<Response> SendRequestAsync(HttpRequestMessage request, CancellationToken token)
{
token.ThrowIfCancellationRequested();
var response = await HttpClient
.SendAsync(request: request, cancellationToken: token).ConfigureAwait(continueOnCapturedContext: false);
token.ThrowIfCancellationRequested();
return await Response.BuildResponse(response);
}
Originally the code worked fine but when I started using Task.WhenAll I started getting 'Rate Limit Exceeded' messages from the API. How can I limit the rate at which requests are made?
Its worth noting that ProcessItem can make between 1-4 API calls depending on the item.
The API is limited to 10 requests per second.
Then just have your code do a batch of 10 requests, ensuring they take at least one second:
Items[] items = ...;
int index = 0;
while (index < items.Length)
{
var timer = Task.Delay(TimeSpan.FromSeconds(1.2)); // ".2" to make sure
var tasks = items.Skip(index).Take(10).Select(i => ProcessItemsAsync(i));
var tasksAndTimer = tasks.Concat(new[] { timer });
await Task.WhenAll(tasksAndTimer);
index += 10;
}
Update
My ProcessItems method makes 1-4 API calls depending on the item.
In this case, batching is not an appropriate solution. You need to limit an asynchronous method to a certain number, which implies a SemaphoreSlim. The tricky part is that you want to allow more calls over time.
I haven't tried this code, but the general idea I would go with is to have a periodic function that releases the semaphore up to 10 times. So, something like this:
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(10);
private async Task<Response> ThrottledSendRequestAsync(HttpRequestMessage request, CancellationToken token)
{
await _semaphore.WaitAsync(token);
return await SendRequestAsync(request, token);
}
private async Task PeriodicallyReleaseAsync(Task stop)
{
while (true)
{
var timer = Task.Delay(TimeSpan.FromSeconds(1.2));
if (await Task.WhenAny(timer, stop) == stop)
return;
// Release the semaphore at most 10 times.
for (int i = 0; i != 10; ++i)
{
try
{
_semaphore.Release();
}
catch (SemaphoreFullException)
{
break;
}
}
}
}
Usage:
// Start the periodic task, with a signal that we can use to stop it.
var stop = new TaskCompletionSource<object>();
var periodicTask = PeriodicallyReleaseAsync(stop.Task);
// Wait for all item processing.
await Task.WhenAll(taskList);
// Stop the periodic task.
stop.SetResult(null);
await periodicTask;
The answer is similar to this one.
Instead of using a list of tasks and WhenAll, use Parallel.ForEach and use ParallelOptions to limit the number of concurrent tasks to 10, and make sure each one takes at least 1 second:
Parallel.ForEach(
items,
new ParallelOptions { MaxDegreeOfParallelism = 10 },
async item => {
ProcessItems(item);
await Task.Delay(1000);
}
);
Or if you want to make sure each item takes as close to 1 second as possible:
Parallel.ForEach(
searches,
new ParallelOptions { MaxDegreeOfParallelism = 10 },
async item => {
var watch = new Stopwatch();
watch.Start();
ProcessItems(item);
watch.Stop();
if (watch.ElapsedMilliseconds < 1000) await Task.Delay((int)(1000 - watch.ElapsedMilliseconds));
}
);
Or:
Parallel.ForEach(
searches,
new ParallelOptions { MaxDegreeOfParallelism = 10 },
async item => {
await Task.WhenAll(
Task.Delay(1000),
Task.Run(() => { ProcessItems(item); })
);
}
);
UPDATED ANSWER
My ProcessItems method makes 1-4 API calls depending on the item. So with a batch size of 10 I still exceed the rate limit.
You need to implement a rolling window in SendRequestAsync. A queue containing timestamps of each request is a suitable data structure. You dequeue entries with a timestamp older than 10 seconds. As it so happens, there is an implementation as an answer to a similar question on SO.
ORIGINAL ANSWER
May still be useful to others
One straightforward way to handle this is to batch your requests in groups of 10, run those concurrently, and then wait until a total of 10 seconds has elapsed (if it hasn't already). This will bring you in right at the rate limit if the batch of requests can complete in 10 seconds, but is less than optimal if the batch of requests takes longer. Have a look at the .Batch() extension method in MoreLinq. Code would look approximately like
foreach (var taskList in tasks.Batch(10))
{
Stopwatch sw = Stopwatch.StartNew(); // From System.Diagnostics
await Task.WhenAll(taskList.ToArray());
if (sw.Elapsed.TotalSeconds < 10.0)
{
// Calculate how long you still have to wait and sleep that long
// You might want to wait 10.5 or 11 seconds just in case the rate
// limiting on the other side isn't perfectly implemented
}
}
https://github.com/thomhurst/EnumerableAsyncProcessor
I've written a library to help with this sort of logic.
Usage would be:
var responses = await AsyncProcessorBuilder.WithItems(items) // Or Extension Method: items.ToAsyncProcessorBuilder()
.SelectAsync(item => ProcessItem(item), CancellationToken.None)
.ProcessInParallel(levelOfParallelism: 10, TimeSpan.FromSeconds(1));

C# tasks with dynamic delay

I have a function that needs to process items 3 at a time, and if the total time taken is less than x seconds, the thread should sleep for the remaining seconds before proceeding further.
So I'm doing the following:
private void ProcessItems()
{
for (int i = 0, n = items.Count; i < n; i++)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
batch.Add(items[i]);
if (batch.Count == 3 || i >= items.Count - 3)
{
List<Task> tasks = new List<Task>(3);
foreach (Item item in batch)
tasks.Add(Task.Factory.StartNew(() => ProcessItem(item)));
Task.WaitAll(tasks.ToArray());
batch.Clear();
}
stopwatch.Stop();
int elapsed = (int)stopwatch.ElapsedMilliseconds;
int delay = (3000) - elapsed;
if (delay > 0)
Thread.Sleep(delay);
}
}
The ProcessItem function makes a webrequest and processes the response (callback). This is the function that takes a small amount of time.
However, if I understand tasks correctly, a thread can have multiple tasks. Therefore, if I sleep the thread, other tasks can be affected.
Is there a more efficient way to achieve the above, and can tasks be used within Parallel.Foreach?
Tasks run on automatically managed threads. There is nothing intrinsically wrong with blocking a thread. It is just a little wasteful.
Here is how I would implement this very cleanly:
MyItem[] items = ...;
foreach(MyItem[] itemsChunk in items.AsChunked(3)) {
Parallel.ForEach(itemsChunk, item => Process(item));
//here you can insert a delay
}
This wastes not a single thread and is trivially simple. Parallel.ForEach used the current thread to process work items as well, so it does not sit idle. You can add your delay logic as well. Implementing AsChunked is left as an exercise for the reader... This function is supposed to split a list into chunks of the given size (3). The good thing about such a helper function is that it untangles the batching logic from the important parts.
Use
Task.Delay
instead
static async Task DoSomeProcess()
{
await Task.Delay(3000);
}
You are right, Thread.Sleep would block other tasks
Yes you can pair async/await pattern with Parallel.
Your ProcessItems method can be very easily transformed into async version ProcessItemsAsync (I didn't validate the "batching" logic):
private async Task ProcessItemsAsync()
{
for (int i = 0, n = items.Count; i < n; i++)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
batch.Add(items[i]);
if (batch.Count == 3 || i >= items.Count - 3)
{
List<Task> tasks = new List<Task>(3);
foreach (Item item in batch)
tasks.Add(Task.Run(() => ProcessItem(item)));
await Task.WhenAll(tasks.ToArray());
batch.Clear();
}
stopwatch.Stop();
int elapsed = (int)stopwatch.ElapsedMilliseconds;
int delay = (3000) - elapsed;
if (delay > 0)
await Task.Delay(delay);
}
}
The only benefit would be that you don't block the ProcessItems thread with Task.WaitAll() and Thread.Sleep(), as #usr pointed out in his answer. Whether to take this approach or Parallel.ForEach one probably depends on the running environment of your code. Async/await won't make your code run faster, but it will improve its scalability for server-side execution, because it may take less threads to run, so more clients could be served.
Note also that now ProcessItemsAsync is itself an async task, so to keep the flow of the code which calls it unchanged, you'd need to call it like this:
ProcessItemsAsync().Wait();
Which is a blocking call on its own and may kill the advantage of async we just gained. Whether you can completely eliminate blocks like this in your app or not, largely depends on the rest of the app's workflow.

how to cancel Task quicker

I have a Windows Service which starts a task on start up
This task which has a while loop and after performing one iteration it go to sleep for 5 minutes.
When I stop service, the task is cancelled first and later some other operations gets performed
if the task is in sleep, it get cancelled only when it wakes up , i want it to be cancelled even if it is sleeping and don't want to wait for waking it up.
following is the code
Task controllerTask = Task.Factory.StartNew(() =>
{
var interval = 300;
while(true)
{
if (cancellationToken.IsCancellationRequested)
break;
Thread.Sleep(interval * 1000);
if (cancellationToken.IsCancellationRequested)
break;
//SOME WORK HERE
}
}, cancellationToken);
Is there any way?
EDIT:
I am not able to use Task.Delay , I can't find it in System.Threading.Tasks.Task namespace , because I am using .Net Framework 4.0 not 4.5
Is there any other better solution that works with 4.0.
Use Task.Delay instead of Thread.Sleep. It takes a CancellationToken parameter so you can abort it before the end of the delay.
If you're using async code, you could write it like this:
await Task.Delay(duration, cancellationToken);
If it's synchronous code, you can just wait the task:
Task.Delay(duration, cancellationToken).Wait();
This is one blocking solution you can use in C# 4.0, VS2010.
cancellationToken.WaitHandle.WaitOne(TimeSpan.FromMinutes(5));
It will unblock when you cancel the token source or on timeout which is your desired sleep interval.
Inspired by the other answers, simple example of using await for this problem:
public static class TaskExtension
{
/// <summary>
/// Call to highlight fact that you do not want to wait on this task.
///
/// This nicely removes resharper warnings without need for comments.
/// </summary>
/// <param name="task"></param>
public static void FireAndForget(this Task task)
{
}
}
internal class Program
{
private static void Main(string[] args)
{
var cancellationToken = new CancellationTokenSource();
TaskCode(cancellationToken.Token).FireAndForget();
Console.ReadLine();
cancellationToken.Cancel();
Console.WriteLine("End");
Console.ReadLine();
}
private static async Task TaskCode(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var interval = TimeSpan.FromSeconds(1);
await Task.Delay(interval, cancellationToken);
//SOME WORK HERE
Console.WriteLine("Tick");
}
}
}
I've broken long sleep into multiple small sleeps, following is the modified code:
Task controllerTask = Task.Factory.StartNew(() =>
{
while(true)
{
if (cancellationToken.IsCancellationRequested) break;
var sleepTime = 10;
if (interval < sleepTime)
interval = sleepTime;
var iteration = (interval / sleepTime);
if ((interval % sleepTime) > 0)
iteration++;
bool cancel = false;
for (int i = 0; i < iteration; i++)
{
Thread.Sleep(sleepTime * 1000);
if (cancellationToken.IsCancellationRequested) { cancel = true; break; };
}
if (cancel) break;
//SOME WORK HERE
}
}

Categories