什么时候使用Task.Delay,什么时候使用Thread.Sleep?


385

是否有何时使用Task.DelayThread.Sleep的良好规则?

  • 具体来说,是否存在一个最小值,以使其中一个比另一个有效/高效?
  • 最后,由于Task.Delay导致在异步/等待状态机上进行上下文切换,因此使用它会产生开销吗?

2
10毫秒是计算机世界中的许多循环……
布拉德·克里斯蒂

应该多快?您有什么性能问题?
LB

4
我认为更相关的问题是,您打算在哪种情况下使用这两种方式?没有这些信息,范围将太广。您所说的有效/有效是什么?您是指精度,功率效率等吗?我很想知道在什么情况下这很重要。
James World

4
最小值为15.625毫秒,小于时钟中断速率的值无效。Task.Delay总是会耗尽System.Threading.Timer,Sleep没有开销。当您编写不执行任何操作的代码时,无需担心开销。
汉斯·帕桑

我没有看到提到的东西,但是我认为很重要的一点是Task.Delay支持CancellationToken,这意味着您可以中断延迟,例如,如果您正在使用它来减慢循环过程。这也意味着您要取消它时,过程可以快速响应。但是您可以使用Thread.Sleep达到相同的目的,从而缩短睡眠周期间隔,并检查Token manuallay。
德罗(Droa)

Answers:


369

使用Thread.Sleep时要阻止当前线程。

使用Task.Delay时,你想不阻塞当前线程的逻辑延迟。

对于这些方法,效率不应该是最重要的问题。它们在现实世界中的主要用途是作为I / O操作的重试计时器,其数量级为秒而不是毫秒。


3
这是相同的主要用例:重试计时器。
Stephen Cleary

4
或者,当您不想占用主循环中的CPU时。
艾迪·帕克

5
@RoyiNamir:不。没有“其他线程”。在内部,它使用计时器来实现。
Stephen Cleary 2014年

20
不担心效率的建议是不明智的。Thread.Sleep将阻止当前线程,这会导致上下文切换。如果使用线程池,这也可能导致分配新线程。两种操作都非常繁重,而Task.Delayetc等提供的协作式多任务设计旨在避免所有这些开销,最大化吞吐量,允许取消并提供更简洁的代码。
Corillian '16

2
@LucaCremry onesi: I would use Thread.Sleep`在同步方法中等待。但是,我从未在生产代码中这样做;以我的经验,Thread.Sleep我所见过的每件事都表明某种设计问题需要适当解决。
史蒂芬·克利西

243

最大的区别Task.DelayThread.Sleep的是,Task.Delay旨在异步运行。Task.Delay在同步代码中使用没有意义。Thread.Sleep在异步代码中使用是一个非常糟糕的主意。

通常,您将Task.Delay() 使用await关键字进行呼叫:

await Task.Delay(5000);

或者,如果您想在延迟之前运行一些代码:

var sw = new Stopwatch();
sw.Start();
Task delay = Task.Delay(5000);
Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
await delay;

猜猜这将打印什么?运行0.0070048秒。如果将鼠标移到await delay上方Console.WriteLine,它将显示运行时间5.0020168秒。

让我们看看与的区别Thread.Sleep

class Program
{
    static void Main(string[] args)
    {
        Task delay = asyncTask();
        syncCode();
        delay.Wait();
        Console.ReadLine();
    }

    static async Task asyncTask()
    {
        var sw = new Stopwatch();
        sw.Start();
        Console.WriteLine("async: Starting");
        Task delay = Task.Delay(5000);
        Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        await delay;
        Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        Console.WriteLine("async: Done");
    }

    static void syncCode()
    {
        var sw = new Stopwatch();
        sw.Start();
        Console.WriteLine("sync: Starting");
        Thread.Sleep(5000);
        Console.WriteLine("sync: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        Console.WriteLine("sync: Done");
    }
}

尝试预测将要打印的内容...

异步:启动
异步:运行0.0070048秒
同步:启动
异步:运行5.0119008秒
异步:完成
同步:运行5.0020168秒
同步:完成

另外,有趣的是,它的Thread.Sleep准确性要高得多,ms的准确性并不是真正的问题,而Task.Delay最小的时间为15-30ms。与它们具有的ms精度相比,这两个函数的开销是最小的(Stopwatch如果需要更准确的信息,请使用Class)。Thread.Sleep仍然束缚您的线程,Task.Delay在等待时释放它以执行其他工作。


15
为什么“在异步代码中使用Thread.Sleep是一个非常糟糕的主意”?
海边

69
@sunside异步代码的主要优点之一是,通过避免阻塞调用,允许一个线程同时处理多个任务。这避免了对大量单个线程的需求,并允许线程池一次处理许多请求。但是,鉴于异步代码通常在线程池上运行,因此不必要地阻塞单个线程Thread.Sleep()会消耗整个线程,而该线程本可以在其他地方使用。如果使用Thread.Sleep()运行许多任务,则很有可能耗尽所有线程池线程并严重影响性能。
Ryan

1
得到它。在async鼓励使用方法的意义上,我缺少异步代码的概念。Thread.Sleep()在线程池线程中运行基本上是一个坏主意,而不是一般的坏主意。毕竟,有TaskCreationOptions.LongRunning(尽管不鼓励)Task.Factory.StartNew()路线。
锡德赛

6
为荣誉await wait
埃里克武

2
@Reyhn这方面的文档Tasl.Delay使用系统计时器。由于“系统时钟以固定速率“滴答”。”,系统计时器的滴答速度约为16毫秒,因此您请求的任何延迟都将四舍五入为系统时钟滴答的数目,并偏移直到第一个滴答滴答的时间。蜱。请参阅Task.Delay docs.microsoft.com/zh-cn/dotnet/api/…上的msdn文档,然后向下滚动至备注。
Dorus

28

如果当前线程被杀死并且您使用Thread.Sleep并且正在执行,那么您可能会得到一个ThreadAbortException。有了Task.Delay可以随时提供取消标记,并优雅地杀死它。那就是我会选择的原因之一Task.Delay。参见http://social.technet.microsoft.com/wiki/contents/articles/21177.visual-c-thread-sleep-vs-task-delay.aspx

我也同意在这种情况下效率不是最重要的。


2
假设我们得到了以下情况:await Task.Delay(5000)。当我杀死任务时,我得到了TaskCanceledException(并抑制了任务),但是我的线程仍然存在。整齐!:)
AlexMelw '17

24

我要添加一些东西。实际上,这Task.Delay是一个基于计时器的等待机制。如果您查看源代码,则会发现Timer对导致延迟的类的引用。另一方面,Thread.Sleep实际上使当前线程进入睡眠状态,那样您就阻塞并浪费了一个线程。在异步编程模型中,Task.Delay()如果您希望某些(连续)延迟后发生,则应始终使用。


'await Task.Delay()'释放线程做其他事情,直到计时器到期(100%清除)。但是,如果由于该方法没有前缀“ async”而无法使用“ await”怎么办?然后,我只能调用“ Task.Delay()”。在那种情况下,线程仍然被阻塞,但是我具有取消Delay()优势。那是对的吗?
Erik Stroeken'5

5
@ErikStroeken您可以将取消令牌传递给线程和任务。Task.Delay()。Wait()将阻塞,而Task.Delay()仅在不等待的情况下创建任务。该任务由您决定,但是线程继续。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.