How can I await a minimum amount of time?

Viewed 2684

I have an async C# method where I am getting an HTTP resource, and I am doing it in an infinite loop. However I don't want to hit the resource too quickly. My current code is:

HttpClient http = new HttpClient();
while (true) {
    // Long-poll the API
    var response = await http.GetAsync(buildUri());

    Console.WriteLine("Resp: " + response.ToString());
    Console.WriteLine("CONTENT:");
    Console.WriteLine(await response.Content.ReadAsStringAsync());
}

However, I want to be able to make sure I don't make an HTTP request more often than once per 10 seconds. So I want to start a 10 second timer at the beginning of the loop, and at the end say "await the completion of the 10 second timer". Is there a way to do this in C#.NET?

5 Answers

At the simplest:

while (true) {
    var rateLimit = Task.Delay(TimeSpan.FromSeconds(10));
    // ...await your http/whatever
    await rateLimit;
}

The await rateLimit will complete immediately if the http work took over the 10 seconds.

However, you may choose to compare the times before and after the http work, to see if you even need to wait; more like:

while (true) {
    var timeBefore = ...
    // ...await your http/whatever
    var timeAfter = ...
    // ... calculate time left
    if (timeLeft > TimeSpan.Zero) await Task.Delay(timeLeft);
}

This avoids the plumbing needed for Task.Delay in the scenarios where it would complete immediately.

Create a StopWatch (and start it) when you start the loop.

Then when GetAsync method returns, you read the ElapsedMilliseconds. Subtract this value from 10000. Now do:

await Task.Delay(yourValue);

Now it takes min. 10 seconds for a loop.

The code:

while (true) {
    // Long-poll the API

    Stopwatch sw = Stopwatch.StartNew();
    var response = await http.GetAsync(buildUri());

    Console.WriteLine("Resp: " + response.ToString());
    Console.WriteLine("CONTENT:");
    Console.WriteLine(await response.Content.ReadAsStringAsync());

    long milliseconds = 10000 - sw.ElapsedMilliseconds;
    if (milliseconds > 0)
    {
        await Task.Delay((int)milliseconds);
    }
}

You can use a simple StopWatch for this, and use Task.Delay to wait the required time, if any.

const int minimumTime = 10_000;
var sw = StopWatch.StartNew();

while (true) 
{
    // Long-poll the API
    var response = await http.GetAsync(buildUri());
    ...
    int difference = minimumTime - sw.EllapsedMiliseconds;

    if (difference > 0)
    {
        await Task.Delay(difference);
    }

    sw.Restart();
}

This, however, is only for the case that you want 10 seconds between each call. If the API takes 9 seconds to complete, you'd get 2 calls in a second.
If you want to always wait 10 seconds, regardless of how much it took the API to complete, just use:

const int waitTime = 10_000;

while (true) 
{
    // Long-poll the API
    var response = await http.GetAsync(buildUri());
    ...

    await Task.Delay(waitTime);
}

Not sure if that's exactly what you are looking for, but Task.Delay can suspend the loop for the given milliseconds.

while (true)
{
 ... 
 await Task.Delay(10 * 1000);
}

You could use CancellationTokenSource combined with TaskCompletionSource to create a "delay" task that starts when your request starts, and which you await when your request is complete:

private static Task GetDelayTask(TimeSpan delay, object state = null)
{
    var taskSource = new TaskCompletionSource<object>();
    var cts = new CancellationTokenSource(delay);
    cts.Token.Register(() =>
    {
        taskSource.SetResult(state);
        cts.Dispose();
    });
    return taskSource.Task;
}

Usage:

HttpClient http = new HttpClient();
while (true)
{
    var delayTask = GetDelayTask(TimeSpan.FromSeconds(10));
    // Long-poll the API
    var response = await http.GetAsync(buildUri());

    Console.WriteLine("Resp: " + response.ToString());
    Console.WriteLine("CONTENT:");
    Console.WriteLine(await response.Content.ReadAsStringAsync());
    await delayTask;
}
Related