For each call emitted to server, I create a new timer by Task.Delay
to watch on its timeout.
Let's say there would be hundreds of concurrent calls. Hence there would be hundreds of Task
counting the timer.
I guess the internal implementation of TPL considered this occasion and all the tasks rely on the same underlying timer?
I am not quite understand the mechanism how the Task.Delay
works internally.
The Delay method is typically used to delay the operation of all or part of a task for a specified time interval. Most commonly, the time delay is introduced: At the beginning of the task, as the following example shows.
Task. Delay does not create new Thread, but still may be heavy, and no guaranties on order of execution or being precise about deadlines.
Delay will create a task which will complete after a time delay. Task. Delay is not blocking the calling thread so the UI will remain responsive.
This function would return immediately. The await keyword is what is causing your execution to be delayed. and now it will delay for 1 second because it is waiting to get the result of the created task for 1 second.
Task.Delay
is implemented with an internal System.Threading.Timer
. That timer class is a wrapper on top of a single native timer. To synchronize access to that single native timer there's an AppDomain
level lock on creating new timers (and changing existing ones). You can see that in the reference source:
internal bool Change(uint dueTime, uint period)
{
// ...
lock (TimerQueue.Instance)
{
// ...
}
// ...
}
In most cases that's fine, but when you create a considerable amount of these timers per second you can get significant contention on that lock. The only way to actually know is to profile your application in a real environment.
I, personally, have reached that point by creating too many self-cancelling CancellationTokenSource
using timers (you can see how I avoided that on my blog: Surprising Contention In System.Threading.Timer
).
There's also this post by Stephen Toub about Coalescing CancellationToken
s from Timeouts that mentions:
"Of course, there are always scenarios the push the boundaries of performance, and we’ve recently seen some high-throughput cases where folks were creating one such
CancellationToken
for each of thousands upon thousands of asynchronous calls being made per second. That’s a lot ofTimer
andCancellationTokenSource
instances."
If approximated delay is acceptable, an alternative is to replace Task.Delay
with HashedWheelTimer.
Code example.
HashedWheelTimer timer = new HashedWheelTimer();
await timer.Delay(1000);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With