Reputation: 148724
I know that Thread.Sleep
blocks a thread.
But does Task.Delay
also block? Or is it just like Timer
which uses one thread for all callbacks (when not overlapping)?
(this question doesn't cover the differences)
Upvotes: 63
Views: 24709
Reputation: 43901
No, the Task.Delay
doesn't block the current thread. It can be used to block it, but it doesn't do it by itself, and it's rarely used as a synchronous blocker in practice. All it actually does is to return a Task
that will complete after the specified amount of time:
Task task = Task.Delay(1000); // The task will complete after 1,000 milliseconds.
Typically this task is then waited asynchronously with the await
keyword, inside an async
method:
await task; // Suspends the async method, but doesn't block the thread.
The await
keyword suspends the current execution flow (async method) until the awaitable completes. No thread is blocked while the execution flow is suspended.
It is also possible to block the current thread until the task completes, by using the synchronous Wait
method.
task.Wait(); // Blocks the thread.
If you would like to see an experimental demonstration that the await Task.Delay()
doesn't block a thread, here is one. The program below creates a huge number of tasks, where each task awaits internally a Task.Delay(1000)
. Then the number of threads used by the current process is printed in the console, and finally all of the tasks are awaited:
Task[] tasks = Enumerable.Range(1, 100_000).Select(async _ =>
{
await Task.Delay(1000);
}).ToArray();
Console.WriteLine($"Tasks: {tasks.Count(t => t.IsCompleted):#,0} / {tasks.Length:#,0}");
Thread.Sleep(500);
Console.WriteLine($"Threads.Count: {Process.GetCurrentProcess().Threads.Count:#,0}");
await Task.WhenAll(tasks);
Console.WriteLine($"Tasks: {tasks.Count(t => t.IsCompleted):#,0} / {tasks.Length:#,0}");
Output:
Tasks: 0 / 100,000
Threads.Count: 9
Tasks: 100,000 / 100,000
The program completes after just 1 second, and reports that during its peak it used a total of 9 threads. If each of the 100,000 tasks blocked a thread, we would expect to see 100,000 threads used at that point. Apparently this didn't happen.
Upvotes: 6
Reputation: 39027
The documentation on MSDN is disappointing, but decompiling Task.Delay
using Reflector gives more information:
public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken)
{
if (millisecondsDelay < -1)
{
throw new ArgumentOutOfRangeException("millisecondsDelay", Environment.GetResourceString("Task_Delay_InvalidMillisecondsDelay"));
}
if (cancellationToken.IsCancellationRequested)
{
return FromCancellation(cancellationToken);
}
if (millisecondsDelay == 0)
{
return CompletedTask;
}
DelayPromise state = new DelayPromise(cancellationToken);
if (cancellationToken.CanBeCanceled)
{
state.Registration = cancellationToken.InternalRegisterWithoutEC(delegate (object state) {
((DelayPromise) state).Complete();
}, state);
}
if (millisecondsDelay != -1)
{
state.Timer = new Timer(delegate (object state) {
((DelayPromise) state).Complete();
}, state, millisecondsDelay, -1);
state.Timer.KeepRootedWhileScheduled();
}
return state;
}
Basically, this method is just a timer wrapped inside of a task. So yes, you can say it's just like timer.
Upvotes: 61