Thread.Sleep和Task.Delay的区别?

63
我知道 Thread.Sleep 会阻塞一个线程。

但是,Task.Delay 是否也会阻塞呢?还是它就像使用一个线程处理所有回调的 Timer(不重叠时)一样呢?

此问题 并未涵盖差异)


2
这里有一个小维基,其中包含一个演示,突出了两者之间的一些阻塞差异。 - jxramos
可能是 Thread.Sleep(2500) vs. Task.Delay(2500).Wait() 的重复内容。 - Teoman shipahi
这个回答解决了你的问题吗?[何时使用Task.Delay,何时使用Thread.Sleep?] (https://dev59.com/MWIj5IYBdhLWcg3wmmNE) - T.Todua
2个回答

59

MSDN 上的文档令人失望,但使用 Reflector 反编译 Task.Delay 可以获得更多信息:

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

基本上,这种方法只是一个包装在任务内部的计时器。因此,你可以说它就像计时器一样。


1
它是否会阻塞? - Royi Namir
1
@RoyiNamir 我不明白你的问题。这是一个任务,它是否阻塞完全取决于你使用它的方式。 - Kevin Gosse
18
把 "await Task.Delay(1000)" 理解为异步版本的 Thread.Sleep(1000),因为它不会阻塞。在 GUI 工具中需要等待但不想阻塞用户界面或占用线程时,我会使用它。 - hko

4
不,Task.Delay 不会阻塞当前线程。它可以用来阻塞线程,但它本身并不会这样做,在实践中很少用作同步阻塞器。它实际上只是返回一个在指定时间后完成的Task
Task task = Task.Delay(1000); // The task will complete after 1,000 milliseconds.

通常,这个任务会在async方法内使用await关键字进行异步等待:

await task; // Suspends the async method, but doesn't block the thread.
await 关键字会暂停当前执行流程(异步方法),直到可等待对象完成。在执行流程被暂停时,没有线程被阻塞。
也可以使用同步 Wait 方法来阻塞当前线程,直到任务完成。
task.Wait(); // Blocks the thread.

如果您想查看一个实验演示,证明 await Task.Delay() 不会阻塞线程,这里有一个。下面的程序创建了大量的任务,其中每个任务都在内部等待 Task.Delay(1000)。然后,在控制台中打印当前进程使用的 线程数量,最后等待所有任务完成:
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}");

输出:

Tasks: 0 / 100,000
Threads.Count: 9
Tasks: 100,000 / 100,000

在线演示

程序只用了1秒钟就完成了,并报告在峰值时使用了总共9个线程。如果每个100,000个任务都阻塞一个线程,我们预计会在那个时候看到使用100,000个线程。显然,这并没有发生。


只是一条注释,但在 WhenAll 之后的线程计数为 14。 - Josh Mouch
@JoshMouch 我猜当完成了100,000个任务时,每个任务都会短暂地使用ThreadPool中的一个线程。因此,ThreadPool创建了一些线程来满足需求,可能是4个线程。这可以解释总线程数从9增加到13的原因。我对第14个线程没有理论。 - Theodor Zoulias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接