何时使用Task.Delay,何时使用Thread.Sleep?

Tom*_* K. 343 c# multithreading task-parallel-library

什么时候使用Task.DelayThread.Sleep有很好的规则?

  • 具体来说,是否有一个最小值来提供一个有效/高效的另一个?
  • 最后,由于Task.Delay导致异步/等待状态机上下文切换,是否有使用它的开销?

Ste*_*ary 341

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

Task.Delay在不阻塞当前线程的情况下使用逻辑延迟.

效率不应成为这些方法的首要考虑因素.它们的主要实际用途是作为I/O操作的重试计时器,其大小为秒而不是毫秒.

  • 在哪些情况下我们需要阻止当前线程? (29认同)
  • 建议不要担心效率是不明智的.`Thread.Sleep`将阻塞导致上下文切换的当前线程.如果您正在使用线程池,这也可能导致分配新线程.这两个操作都非常繁重,而`Task.Delay`等提供的协作式多任务处理旨在避免所有这些开销,最大化吞吐量,允许取消,并提供更清晰的代码. (12认同)
  • @RoyiNamir:不.没有"其他线程".在内部,它是用计时器实现的. (5认同)
  • 或者当你不想在主循环中咀嚼CPU时. (4认同)
  • 它是相同的主要用例:重试计时器. (3认同)
  • @LucaCremry `onesi:我会使用 `Thread.Sleep` 在同步方法中等待。但是,我从未在生产代码中这样做;根据我的经验,我见过的每一个 `Thread.Sleep` 都表明某种需要正确修复的设计问题。 (3认同)
  • @StephenCleary:当我们必须在同步方法中等待时,您是否建议使用“Thread.Sleep”而不是“Task.Delay(delay).Wait()”?在这种情况下,我没有看到使用“Task.Delay(delay).Wait()”有任何优势。 (2认同)

Dor*_*rus 216

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

通常您会Task.Delay() 使用await关键字调用:

await Task.Delay(5000);
Run Code Online (Sandbox Code Playgroud)

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

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

猜猜这会印刷什么?运行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");
    }
}
Run Code Online (Sandbox Code Playgroud)

试着预测这会印刷什么......

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

此外,有趣的是注意到Thread.Sleep更准确,ms精度并不是真正的问题,而Task.Delay最小可能需要15-30ms.两个函数的开销与它们具有的ms精度相比是最小的(Stopwatch如果您需要更精确的东西,请使用Class).Thread.Sleep仍然束缚你的线程,Task.Delay在你等待时释放它做其他工作.

  • @sunside异步代码的一个主要优点是允许一个线程同时处理多个任务,避免阻塞调用.这避免了对大量单个线程的需求,并允许线程池一次为多个请求提供服务.但是,鉴于异步代码通常在线程池上运行,不必要地使用`Thread.Sleep()`阻塞单个线程会消耗一个原本可以在其他地方使用的线程.如果使用Thread.Sleep()运行许多任务,则很有可能耗尽所有线程池线程并严重阻碍性能. (60认同)
  • 为什么在异步代码中使用Thread.Sleep是"一个非常糟糕的主意"? (13认同)
  • 感谢`等待' (5认同)
  • @Reyhn 关于此的文档是`Tasl.Delay` 使用系统计时器。由于“系统时钟以恒定速率“滴答”,系统计时器的滴答速度约为 16 毫秒,因此您请求的任何延迟都将四舍五入为系统时钟的若干滴答声,偏移到第一个的时间打钩。请参阅有关 `Task.Delay` https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.delay 的 msdn 文档并向下滚动到备注。 (3认同)

Bal*_*kas 26

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

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

  • 假设我们遇到以下情况:`await Task.Delay(5000)`。当我杀死任务时,我得到`TaskCanceledException`(并取消它),但是我的线程仍然存在。整齐!:) (2认同)

cry*_*ted 20

我想补充一些东西.实际上,Task.Delay是基于计时器的等待机制.如果您查看源代码,您会找到一个对Timer延迟负责的类的引用.另一方面,Thread.Sleep实际上让当前线程处于睡眠状态,这样你只是阻塞并浪费一个线程.在异步编程模型中,Task.Delay()如果你想在延迟之后发生某些事情(延续),你应该总是使用它.

  • @ErikStroeken您可以将取消令牌传递给线程和任务.Task.Delay().Wait()将阻塞,而Task.Delay()只是创建任务,如果没有等待使用.你用这个任务做什么取决于你,但线程还在继续. (5认同)

小智 10

Delayed会是一个更好的名称Task.Delay- 因为它不会延迟现有任务,而是创建一个新的“延迟”任务,另一方面可以等待该任务并可能导致当前任务主体挂起。它本质上是一个计时器,但没有回调/主体。

等待延迟任务会在异步消息队列中创建一个新项目,并且不会阻塞任何线程。调用等待的同一线程将继续处理其他任务(如果有),并将在超时后(或当队列中的前面的项目完成时)返回到等待点。后台任务使用线程 - 可以在单个线程中调度和执行许多任务。另一方面,如果您碰巧调用Thread.Sleep()该线程,该线程将被阻塞,即它将在请求的时间内停止运行,并且不会处理队列中的任何异步消息。

在.NET 中,有两种主要的并行方法。旧的有线程、线程池等。新的则基于任务、异步/等待、TPL。根据经验,您不要混合这两个领域的 API。