简体   繁体   English

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

[英]When to use Task.Delay, when to use Thread.Sleep?

Are there good rule(s) for when to use Task.Delay versus Thread.Sleep ?何时使用Task.DelayThread.Sleep是否有好的规则?

  • Specifically, is there a minimum value to provide for one to be effective/efficient over the other?具体来说,是否有一个最小值来规定一个比另一个有效/高效?
  • Lastly, since Task.Delay causes context-switching on a async/await state machine, is there an overhead of using it?最后,由于 Task.Delay 导致异步/等待状态机上的上下文切换,使用它是否有开销?

Use Thread.Sleep when you want to block the current thread.当你想阻塞当前线程时使用Thread.Sleep

Use Task.Delay when you want a logical delay without blocking the current thread.当您想要一个逻辑延迟而不阻塞当前线程时,请使用Task.Delay

Efficiency should not be a paramount concern with these methods.效率不应该是这些方法的首要问题。 Their primary real-world use is as retry timers for I/O operations, which are on the order of seconds rather than milliseconds.它们在现实世界中的主要用途是作为 I/O 操作的重试计时器,其数量级是秒而不是毫秒。

The biggest difference between Task.Delay and Thread.Sleep is that Task.Delay is intended to run asynchronously. Task.DelayThread.Sleep的最大区别在于Task.Delay旨在异步运行。 It does not make sense to use Task.Delay in synchronous code.在同步代码中使用Task.Delay没有意义。 It is a VERY bad idea to use Thread.Sleep in asynchronous code.在异步代码中使用Thread.Sleep是一个非常糟糕的主意。

Normally you will call Task.Delay() with the await keyword:通常你会用await关键字调用Task.Delay()

await Task.Delay(5000);

or, if you want to run some code before the delay:或者,如果您想在延迟之前运行一些代码:

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

Guess what this will print?猜猜这会打印什么? Running for 0.0070048 seconds.运行 0.0070048 秒。 If we move the await delay above the Console.WriteLine instead, it will print Running for 5.0020168 seconds.如果我们将await delay移到Console.WriteLine上方,它将打印 Running for 5.0020168 seconds。

Let's look at the difference with Thread.Sleep :让我们看看与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");
    }
}

Try to predict what this will print...尝试预测这将打印什么...

async: Starting异步:开始
async: Running for 0.0070048 seconds异步:运行 0.0070048 秒
sync: Starting同步:开始
async: Running for 5.0119008 seconds异步:运行 5.0119008 秒
async: Done异步:完成
sync: Running for 5.0020168 seconds同步:运行 5.0020168 秒
sync: Done同步:完成

Also, it is interesting to notice that Thread.Sleep is far more accurate, ms accuracy is not really a problem, while Task.Delay can take 15-30ms minimal.此外,有趣的是, Thread.Sleep更准确,毫秒精度不是问题,而Task.Delay最少需要 15-30 毫秒。 The overhead on both functions is minimal compared to the ms accuracy they have (use Stopwatch Class if you need something more accurate).与它们的毫秒精度相比,这两个函数的开销都是最小的(如果您需要更准确的东西,请使用Stopwatch类)。 Thread.Sleep still ties up your Thread, Task.Delay release it to do other work while you wait. Thread.Sleep仍然会占用您的 Thread, Task.Delay在您等待时释放它以执行其他工作。

I want to add something.我想补充一点。 Actually, Task.Delay is a timer based wait mechanism.实际上, Task.Delay是一种基于定时器的等待机制。 If you look at the source you would find a reference to a Timer class which is responsible for the delay.如果您查看源代码,您会发现对负责延迟的Timer类的引用。 On the other hand Thread.Sleep actually makes current thread to sleep, that way you are just blocking and wasting one thread.另一方面Thread.Sleep实际上使当前线程进入睡眠状态,这样你只是阻塞和浪费了一个线程。 In async programming model you should always use Task.Delay() if you want something(continuation) happen after some delay.在异步编程模型中,如果您希望在延迟后发生某些事情(继续),您应该始终使用Task.Delay()

if the current thread is killed and you use Thread.Sleep and it is executing then you might get a ThreadAbortException .如果当前线程被杀死并且您使用Thread.Sleep并且它正在执行,那么您可能会得到一个ThreadAbortException With Task.Delay you can always provide a cancellation token and gracefully kill it.使用Task.Delay ,您始终可以提供取消令牌并优雅地终止它。 Thats one reason I would choose Task.Delay .这就是我选择Task.Delay的原因之一。 see http://social.technet.microsoft.com/wiki/contents/articles/21177.visual-c-thread-sleep-vs-task-delay.aspxhttp://social.technet.microsoft.com/wiki/contents/articles/21177.visual-c-thread-sleep-vs-task-delay.aspx

I also agree efficiency is not paramount in this case.我也同意在这种情况下效率并不是最重要的。

Delayed would be a better name for Task.Delay - because it doesn't delay an existing task but rather creates a new 'delayed' one which on the other hand can be awaited and can cause suspension to the current task's body. Delayed将是Task.Delay的一个更好的名称 - 因为它不会延迟现有任务,而是创建一个新的“延迟”任务,另一方面,它可以等待并且可能导致当前任务的主体暂停。 It is essentially a Timer but without a callback/body.它本质上是一个计时器,但没有回调/正文。

Awaiting a delayed task creates a new item in async message queue and doesn't block any threads.等待延迟任务会在异步消息队列中创建一个新项目,并且不会阻塞任何线程。 The same thread where the await is called will proceed working on other tasks should there be any, and will return to the await point after the timeout (or when the preceding items in queue are complete).如果有其他任务,调用 await 的同一线程将继续处理其他任务,并在超时后(或队列中的前面项目完成时)返回等待点。 Tasks under the hood use Threads - there can be many Tasks scheduled and executed in a single thread.后台任务使用线程 - 可以在单个线程中安排和执行许多任务。 On the other hand if you happen to call Thread.Sleep() the thread will block, ie it will be out of play for the amount of time asked and won't process any async messages from the queue.另一方面,如果你碰巧调用了Thread.Sleep()线程将被阻塞,也就是说,它会在请求的时间内停止运行,并且不会处理来自队列的任何异步消息。

In .NET there're 2 major approaches to parallelism.在 .NET 中有两种主要的并行方法。 The old one with Threads, ThreadPools etc. And the new one, based on Tasks, async/await, TPL.旧的有线程、线程池等。新的基于任务、异步/等待、TPL。 As a rule of thumb you don't mix APIs from these two universes.根据经验,您不会混合来自这两个领域的 API。

It is also worth to mention that Thread.Sleep(1) will fire GC faster.值得一提的是 Thread.Sleep(1) 会更快地触发 GC。

This is purely based mine & team member observations.这纯粹是基于我和团队成员的观察。 Lets assume that you have service which creates new task every for specific request (approx. 200-300 ongoing) and this task contains many weak references in flow.让我们假设您有一个服务,它为每个特定请求(大约 200-300 个正在进行)创建新任务,并且该任务包含许多流中的弱引用。 The task is working like state machine so we were firing the Thread.Sleep(1) on change state and by doing so we managed to optimize utilization of memory in the application - like I said before - this will makes GC to fire faster.该任务就像状态机一样工作,因此我们在更改状态时触发 Thread.Sleep(1),通过这样做,我们设法优化了应用程序中内存的利用率——就像我之前所说的——这将使 GC 更快地触发。 It doesn't make so much difference in low memory consumption services (<1GB).它在低内存消耗服务(<1GB)方面没有太大区别。

I had a long argument with a colleague about this and he proved to me that there are significant differences beyond what the top answer currently shows.我与一位同事就此进行了长时间的争论,他向我证明,除了目前的最佳答案之外,存在显着差异。 If you await Task.Delay(SomeMilliseconds) you can actually release callers other than your immediate parent on the stack:如果您await Task.Delay(SomeMilliseconds)您实际上可以在堆栈上释放您的直接父级以外的调用者:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Started " + Thread.CurrentThread.ManagedThreadId);
            DoSomething1();
            Console.WriteLine("Finished " + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(6000);
        }

        static async void DoSomething1()
        {
            Console.WriteLine("DoSomething1 Started " + Thread.CurrentThread.ManagedThreadId);
            var result = await DoSomething2();
            Console.WriteLine("DoSomething1 Finished " + Thread.CurrentThread.ManagedThreadId);
        }

        static async Task<int> DoSomething2()
        {
            Console.WriteLine("DoSomething2 Started " + Thread.CurrentThread.ManagedThreadId);

            await Task.Delay(5000);         // Will block DoSomething1 but release Main
            //Thread.Sleep(5000);           // Will block everything including Main
            //await Task.FromResult(5);     // Will return immediately (just for comparison)
            //await Task.Delay(0);          // What will it do, can you guess?

            Console.WriteLine("DoSomething2 Finished " + Thread.CurrentThread.ManagedThreadId);
            return 0;
        }
    }
}

Play with this code and observe the different effects of using Delay or using Sleep .使用此代码并观察使用Delay或使用Sleep的不同效果。 The explanation is beyond the scope of this answer but can be summed up as "async functions don't start another thread until they await something that can't be immediately run (or the outcome determined)".解释超出了这个答案的范围,但可以总结为“异步函数在等待无法立即运行的东西(或确定的结果)之前不会启动另一个线程”。 This is the output:这是输出:

Started 1
DoSomething1 Started 1
DoSomething2 Started 1
Finished 1
DoSomething2 Finished 4
DoSomething1 Finished 4

This isn't about DoSomething1();这不是关于DoSomething1(); in Main being fire and forget.Main是火和忘记。 You can prove that by using the Sleep .您可以通过使用Sleep来证明这一点。 Also observe that when DoSomething2 "returns" from Task.Delay, it's running on a different thread.另请注意,当 DoSomething2 从 Task.Delay “返回”时,它在不同的线程上运行。

This stuff is much smarter than I gave it credit for, believing that await just started a new thread to do stuff.这个东西比我认为的要聪明得多,相信await刚刚开始了一个新线程来做一些事情。 I still don't pretend to understand it all, but the counter-intuitive result above shows there's much more going on under the hood than just starting threads to run code.我仍然没有假装理解这一切,但上面违反直觉的结果表明,除了启动线程来运行代码之外,还有更多的事情要做。

My opinion,我的意见,

Task.Delay() is asynchronous. Task.Delay()是异步的。 It doesn't block the current thread.它不会阻塞当前线程。 You can still do other operations within current thread.您仍然可以在当前线程中执行其他操作。 It returns a Task return type ( Thread.Sleep() doesn't return anything ).它返回一个 Task 返回类型( Thread.Sleep()不返回任何东西)。 You can check if this task is completed(use Task.IsCompleted property) later after another time-consuming process.您可以在另一个耗时的过程之后检查此任务是否已完成(使用Task.IsCompleted属性)。

Thread.Sleep() doesn't have a return type. Thread.Sleep()没有返回类型。 It's synchronous.是同步的。 In the thread, you can't really do anything other than waiting for the delay to finish.在线程中,除了等待延迟完成之外,您实际上无法做任何事情。

As for real-life usage, I have been programming for 15 years.至于现实生活中的使用,我已经编程了 15 年。 I have never used Thread.Sleep() in production code.我从未在生产代码中使用过Thread.Sleep() I couldn't find any use case for it.我找不到任何用例。 Maybe that's because I mostly do web application development.也许那是因为我主要从事 Web 应用程序开发。

In an async program, the difference between在异步程序中,两者的区别

await task.Delay() 
//and 
thread.sleep 

is nominal in a simple app, one might be more cancellable, one might be more accurate, one might be a tiny bit faster... but at the end of the day, both do the same thing, they block the executing code.在一个简单的应用程序中是名义上的,一个可能更可取消,一个可能更准确,一个可能更快一点……但归根结底,两者都做同样的事情,它们会阻止执行代码。 .. ..

Here are the results:结果如下:

1 00:00:00.0000767
Not Delayed.
1 00:00:00.2988809
Delayed 1 second.
4 00:00:01.3392148
Delayed 3 second.
5 00:00:03.3716776
Delayed 9 seconds.
5 00:00:09.3838139
Delayed 10 seconds
4 00:00:10.3411050
4 00:00:10.5313519

From this code:从这段代码:

var sw = new Stopwatch();
sw.Start();
Console.WriteLine($"{sw.Elapsed}");
var asyncTests = new AsyncTests();

var go1 = asyncTests.WriteWithSleep();
var go2 = asyncTests.WriteWithoutSleep();

await go1;
await go2;
sw.Stop();
Console.WriteLine($"{sw.Elapsed}");
        
Stopwatch sw1 = new Stopwatch();
Stopwatch sw = new Stopwatch();
    public async Task WriteWithSleep()
    {
        sw.Start();
        var delayedTask =  Task.Delay(1000);
        Console.WriteLine("Not Delayed.");
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
        await delayedTask;
        Console.WriteLine("Delayed 1 second.");
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
        Thread.Sleep(9000);
        Console.WriteLine("Delayed 10 seconds");
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
        sw.Stop();
    }
    public async Task WriteWithoutSleep()
    {
        await Task.Delay(3000);
        Console.WriteLine("Delayed 3 second.");
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
        await Task.Delay(6000);
        Console.WriteLine("Delayed 9 seconds.");
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
    }

Sleep acts the same way as an immediate await, except it blocks the thread.睡眠的作用与立即等待相同,只是它阻塞了线程。 A task that is assigned to a var may cause a thread switch when it is finally awaited.分配给 var 的任务在最终等待时可能会导致线程切换。 In this example, it looks like the code starts on thread 1, then creates thread 5 for WriteWithoutSleep(), but continues executing on thread 1 for ThreadWithSleep() until delayedTask is awaited.在此示例中,代码从线程 1 开始,然后为 WriteWithoutSleep() 创建线程 5,但继续在线程 1 上为 ThreadWithSleep() 执行,直到等待延迟任务。 At that moment, thread 1's code flows into thread 4 and further execution in Main is now on thread 4;此时,线程 1 的代码流入线程 4,在 Main 中的进一步执行现在在线程 4 上; thread 1 is for lack of a better word thrown away.线程 1 是因为缺少更好的词而被丢弃。

All of the above answers are very valuable.以上所有答案都非常有价值。 However, In a simple console app, it doesn't seem to matter except maybe academically over the course of several runs which you use if you immediately await your Task.Delay() and don't intend to use cancellation tokens;但是,在一个简单的控制台应用程序中,如果您立即等待您的 Task.Delay() 并且不打算使用取消令牌,那么它似乎并不重要,除非在您使用的几次运行过程中学术上;

In a complex app, putting threads to sleep vs. jumping from thread to thread due to creating tasks and awaiting them later vs. immediate awaits could be points of consideration.在一个复杂的应用程序中,由于创建任务并在稍后等待它们而不是立即等待而使线程进入睡眠状态与从一个线程跳转到另一个线程可能是需要考虑的问题。

Finally, putting a Process.GetCurrentProcess().Threads.Count at the beginning of a console app (at least mine) produced 13 threads in debugger mode.最后,将 Process.GetCurrentProcess().Threads.Count 放在控制台应用程序(至少我的)的开头会在调试器模式下产生 13 个线程。 After the awaited calls, I had 17 threads in debugger mode in visual studio.在等待调用之后,我在 Visual Studio 中有 17 个处于调试器模式的线程。 I've read that ConsoleApps have only 3 threads and the rest are debugger threads, but running the consoleApp without debugging in visual studio resulted in 8 and then 14 threads.我读过 ConsoleApps 只有 3 个线程,其余的是调试器线程,但是在 Visual Studio 中不调试就运行 consoleApp 会导致 8 个线程,然后是 14 个线程。 Running it outside visual studio resulted in 8 then 14 threads.在 Visual Studio 之外运行它会产生 8 个然后 14 个线程。

Copying the code and pasting it just afterwards had the same number of the threads, 8,14 and everything stayed on threads 4 and 5. The second thread.sleep and task.delays did not cause thread jumps.复制代码并随后粘贴它具有相同数量的线程,8,14 并且所有内容都保留在线程 4 和 5 上。第二个 thread.sleep 和 task.delays 没有导致线程跳转。 And all of this research is to propose: while thread.sleep will block a thread and task.delay will not and has a cancellation token, unless your app is pretty complex, it really doesn't matter as on the surface: task.delay and thread.sleep do pretty much the same thing.所有这些研究都是为了建议:虽然 thread.sleep 会阻塞一个线程,而 task.delay 不会并且有一个取消令牌,除非您的应用程序非常复杂,否则这实际上并不重要,因为表面上:task.delay和 thread.sleep 做几乎相同的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM