是否有很好的规则来说明何时使用Task。Delay vs . Thread.Sleep?

具体来说,是否存在一个最小值来保证其中一个比另一个更有效? 最后,自从任务。延迟导致异步/等待状态机上的上下文切换,是否有使用它的开销?


当前回答

我和一位同事为此争论了很长时间,他向我证明,在上面的答案目前所显示的范围之外,还有显著的差异。如果你等待Task.Delay(somemillisecseconds),你实际上可以释放堆栈上的直接父对象之外的调用者:

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

玩一下这段代码,观察使用Delay或Sleep的不同效果。这个解释超出了这个答案的范围,但可以总结为“异步函数不会启动另一个线程,直到它们等待一些不能立即运行的东西(或结果确定)”。输出如下:

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

这不是关于DoSomething1();在Main被大火遗忘。你可以用Sleep来证明这一点。还要观察当DoSomething2从Task“返回”时。延迟,它在另一个线程上运行。

这个东西比我给它的信用要聪明得多,相信await只是开始了一个新的线程来做事情。我仍然没有假装完全理解,但上面的反直觉结果表明,在底层有更多的事情要做,而不仅仅是启动线程来运行代码。

其他回答

我和一位同事为此争论了很长时间,他向我证明,在上面的答案目前所显示的范围之外,还有显著的差异。如果你等待Task.Delay(somemillisecseconds),你实际上可以释放堆栈上的直接父对象之外的调用者:

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

玩一下这段代码,观察使用Delay或Sleep的不同效果。这个解释超出了这个答案的范围,但可以总结为“异步函数不会启动另一个线程,直到它们等待一些不能立即运行的东西(或结果确定)”。输出如下:

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

这不是关于DoSomething1();在Main被大火遗忘。你可以用Sleep来证明这一点。还要观察当DoSomething2从Task“返回”时。延迟,它在另一个线程上运行。

这个东西比我给它的信用要聪明得多,相信await只是开始了一个新的线程来做事情。我仍然没有假装完全理解,但上面的反直觉结果表明,在底层有更多的事情要做,而不仅仅是启动线程来运行代码。

我的观点,

Task.Delay()是异步的。它不会阻塞当前线程。您仍然可以在当前线程中执行其他操作。它返回一个Task返回类型(Thread.Sleep()不返回任何东西)。您可以检查该任务是否已完成(使用task。IsCompleted属性),稍后在另一个耗时的过程之后。

Thread.Sleep()没有返回类型。它是同步的。在线程中,除了等待延迟完成之外,您实际上不能做任何事情。

至于在现实生活中的使用,我已经编程15年了。我从未在产品代码中使用过Thread.Sleep()。我找不到任何用例。 也许是因为我主要从事web应用程序开发。

我想补充一点。 实际上,任务。Delay是一种基于定时器的等待机制。如果您查看源代码,您会发现一个Timer类的引用,它是造成延迟的原因。另一方面,线程。Sleep实际上使当前线程休眠,这样你只是阻塞和浪费了一个线程。在异步编程模型中,如果你想在延迟后发生某些事情(延续),你应该总是使用Task.Delay()。

是的,对于何时使用Task有一些一般的指导方针。Delay vs . Thread.Sleep。的任务。延迟是c#中异步编程的首选方法,因为它允许您在不阻塞线程的情况下异步等待。线程。Sleep阻塞调用线程,并可能导致高并发性应用程序的性能问题。

Task没有最小值。延迟对Thread.Sleep有效。一般来说,如果你需要等待一定的时间,最好使用Task.Delay。如果你需要在特定的时间内阻塞一个线程,请使用thread . sleep。

至于使用Task的开销。在异步/等待状态机中,上下文切换涉及一些开销。但是,开销通常很小,而且异步编程的好处远远超过了它。当使用Task。正确地延迟,它可以帮助提高应用程序的响应性和可伸缩性。

在异步程序中,两者的区别

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

在一个简单的应用程序中,一个可能更可取消,一个可能更准确,一个可能更快……但在一天结束的时候,两者都做同样的事情,他们阻塞正在执行的代码…

以下是调查结果:

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

从这段代码:

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. 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. At that moment, thread 1's code flows into thread 4 and further execution in Main is now on thread 4; thread 1 is for lack of a better word thrown away.

以上所有的答案都很有价值。然而,在一个简单的控制台应用程序中,它似乎并不重要,除非在你使用的几次运行过程中,如果你立即等待你的Task.Delay()并且不打算使用取消令牌;

在一个复杂的应用程序中,让线程进入睡眠状态,还是因为创建任务而从一个线程跳到另一个线程,然后等待它们,还是立即等待,这是需要考虑的问题。

最后,放入Process.GetCurrentProcess(). threads。控制台应用程序(至少是我的)开头的Count在调试器模式下产生了13个线程。在等待调用之后,我在visual studio的调试器模式下有17个线程。我曾经读到consoleApp只有3个线程,其余的都是调试器线程,但是在没有调试的情况下运行consoleApp会导致8个线程和14个线程。在visual studio外部运行它的结果是8个线程和14个线程。

复制代码并粘贴它之后,线程数是相同的,8,14,所有内容都停留在线程4和5上。第二个线程。Sleep和task.delay不会导致线程跳转。所有这些研究都是为了提出:while线程。Sleep会阻塞一个线程,task.delay不会,并且有一个取消令牌,除非你的应用程序非常复杂,它真的不重要,因为表面上:task.delay和thread。睡眠也做着同样的事情。