How to Use Task.Delay as a Timer

Can I use Task.Delay as a timer?

Microsoft's Reactive Framework is ideal for this. Just NuGet "System.Reactive" to get the bits. Then you can do this:

IDisposable subscription =
Observable
.Interval(TimeSpan.FromSeconds(1.0))
.Subscribe(x => execute());

When you want to stop the subscription just call subscription.Dispose(). On top of this the Reactive Framework can offer far more power than Tasks or basic Timers.

Repeated Tasks using Timer Interval vs Task Delay

Method A is used when you have to trigger some task at regular intervals. Method B is used when you have to give regular intervals between the triggering of some task.

There is a difference if the event handler (task performed after the trigger) takes a longer time to process.

Method A :
No matter how much time the task in the event handler takes, the event handler keeps getting triggered at the set time interval (code has to be reentrant).

Method B :
Task completes then we sleep. So if the task takes different times to complete, the next trigger is also at different times

"timer + Task.Run" vs "while loop + Task.Delay" in asp.net core hosted service

The while loop approach is simpler and safer. Using the Timer class has two hidden gotchas:

  1. Subsequent events can potentially invoke the attached event handler in an ovelapping manner.
  2. Exceptions thrown inside the handler are swallowed, and this behavior is subject to change in future releases of the .NET Framework. (from the docs)

Your current while loop implementation can be improved in various ways though:

  1. Reading the DateTime.Now multiple times during a TimeSpan calculation may produce unexpected results, because the DateTime returned by DateTime.Now can be different each time. It is preferable to store the DateTime.Now in a variable, and use the stored value in the calculations.
  2. Checking the condition cancellationToken.IsCancellationRequested in the while loop could result to inconsistent cancellation behavior, if you also use the same token as an argument of the Task.Delay. Skipping this check completely is simpler and consistent. This way cancelling the token will always produce an OperationCanceledException as a result.
  3. Ideally the duration of the Process should not affect the scheduling of the next operation. One way to do it is to create the Task.Delay task before starting the Process, and await it after the completion of the Process. Or you can just recalculate the next delay based on the current time. This has also the advantage that the scheduling will be adjusted automatically in case of a system-wise time change.

Here is my suggestion:

public async Task StartAsync(CancellationToken cancellationToken)
{
TimeSpan scheduledTime = TimeSpan.FromHours(0); // midnight
TimeSpan minimumIntervalBetweenStarts = TimeSpan.FromHours(12);

while (true)
{
var scheduledDelay = scheduledTime - DateTime.Now.TimeOfDay;

while (scheduledDelay < TimeSpan.Zero)
scheduledDelay += TimeSpan.FromDays(1);

await Task.Delay(scheduledDelay, cancellationToken);

var delayBetweenStarts =
Task.Delay(minimumIntervalBetweenStarts, cancellationToken);

await ProcessAsync();

await delayBetweenStarts;
}
}

The reason for the minimumIntervalBetweenStarts is to protect from very dramatic system-wise time changes.

await Delay or use timers

I think that first approach is more readable.

Other than that, I can't see any reason for picking either method. If you wait for 1 second between operations, it doesn't really matter what is the performance of that command for sure it will be at least 1000x faster than your waiting period.

What is going on with Task.Delay().Wait()?

I rewrote the posted snippet a bit to get the results ordered better, my brand-new laptop has too many cores to interpret the existing jumbled output well enough. Recording the start and end times of each task and displaying them after they are all done. And recording the actual start time of the Task. I got:

0: 68 - 5031
1: 69 - 5031
2: 68 - 5031
3: 69 - 5031
4: 69 - 1032
5: 68 - 5031
6: 68 - 5031
7: 69 - 5031
8: 1033 - 5031
9: 1033 - 2032
10: 2032 - 5031
11: 2032 - 3030
12: 3030 - 5031
13: 3030 - 4029
14: 4030 - 5031
15: 4030 - 5031

Ah, that suddenly makes a lot of sense. A pattern to always watch for when dealing with threadpool threads. Note how once a second something significant happens and two tp threads start running and some of them can complete.

This is a deadlock scenario, similar to this Q+A but otherwise without the more disastrous outcome of that user's code. The cause is next-to-impossible to see since it is buried in .NETFramework code, you'd have to look how Task.Delay() is implemented to make sense of it.

The relevant code is here, note how it uses a System.Threading.Timer to implement the delay. A gritty detail about that timer is that its callback is executed on the threadpool. Which is the basic mechanism by which Task.Delay() can implement the "you don't pay for what you don't use" promise.

The gritty detail is that this can take a while if the threadpool is busy churning away at threadpool execution requests. It's not the timer is slow, the problem is that the callback method just doesn't get started soon enough. The problem in this program, Task.Run() added a bunch of requests, more than can be executed at the same time. The deadlock occurs because the tp-thread that was started by Task.Run() cannot complete the Wait() call until the timer callback executes.

You can make it a hard deadlock that hangs the program forever by adding this bit of code to the start of Main():

     ThreadPool.SetMaxThreads(Environment.ProcessorCount, 1000);

But the normal max-threads is much higher. Which the threadpool manager takes advantage of to solve this kind of deadlock. Once a second it allows two more threads than the "ideal" number of them to execute when the existing ones don't complete. That's what you see back in the output. But it is only two at a time, not enough to put much of a dent in the 8 busy threads that are blocked on the Wait() call.

The Thread.Sleep() call does not have this problem, it doesn't depend on .NETFramework code or the threadpool to complete. It is the OS thread scheduler that takes care of it, and it always runs by virtue of the clock interrupt. Thus allowing new tp threads to start executing every 100 or 300 msec instead of once a second.

Hard to give concrete advice to avoid such a deadlock trap. Other than the universal advice, always avoid having worker threads block.

When to use Task.Delay, when to use Thread.Sleep?

Use Thread.Sleep when you want to block the current thread.

Use Task.Delay when you want a logical delay without blocking the current thread.

Efficiency should not be a paramount concern with these methods. Their primary real-world use is as retry timers for I/O operations, which are on the order of seconds rather than milliseconds.

How does Task.Delay work exactly?

It takes 3 seconds to print "One" because you await-ed Task.Delay too soon.

Change the code as follows to get the result that you expected:

int result = await Task.Run(() => Multiply(m, n));
var taskDelay = Task.Delay(3000); // No blocking here, so
Console.WriteLine("One"); // printing can proceed.
await taskDelay; // This causes a block for the remainder of 3 seconds
Console.WriteLine(result);

When you start the delay task prior to printing "One" without await-ing it, subsequent WriteLine could complete without a delay.

Task.Delay vs Thread.Sleep for suspending System.Timers.Timer's job

It depends on what your goal is.

Thread.Sleep will block the system thread that is in use. By default System.Timers.Timer uses the system thread-pool, meaning you wouldn't block your main thread, and the use of Thread.Sleep probably will execute concurrently with the rest of your program. If you desire is to use (and therefore block) a specific thread, you will need use the SynchronizingObject property, in coordination with Thread.Sleep.

Task.Delay will provide a logical delay without blocking the current thread. This is the best approach, unless you have a reason to block a specific thread. Keep in mind you'll need to make your event handler asynchronous to use Task.Delay:

 timer.Elapsed += async (sender, args) =>
{
for (int i = 0; i < 15; i++)
{
await Task.Delay(1000);
...
}
}

Is Task.Delay truly asynchronous like an I/O operation is, i.e. does it rely on hardware and interrupts instead of a thread?

In the current implementation of .NET, there is a single "timer thread" that just keeps track of managed timer instances and raises their events at the appropriate times. This timer thread will block on its control signal with a timeout set to the next timer's due time. The control signal is used to add/remove/change timers, so when this blocking request times out, the timer thread knows the next timer has fired. This is a normal thread blocking operation, so internally, the thread is idled and removed from the scheduler queue until that blocking operation completes or is timed out. The timing out of those operations is handled by the OS scheduler's timer interrupt.

So technically there is a thread, but it's only one thread per process, not one thread per Task.Delay.

I again stress that this is in the current implementation of .NET. Other solutions have been proposed, such as one timer thread per CPU, or a dynamic pool of timer threads. Perhaps they were experimented with and rejected for some reason, or perhaps an alternative solution will be adopted in the future. AFAIK this is not officially documented anywhere, so this is an implementation detail.



Related Topics



Leave a reply



Submit