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

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


当前回答

“任务”的名称应该是“延迟”。延迟——因为它不会延迟现有的任务,而是创建一个新的“延迟”任务,另一方面可以等待,并可能导致当前任务主体挂起。它本质上是一个Timer,但没有回调/主体。

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). 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, i.e. it will be out of play for the amount of time asked and won't process any async messages from the queue.

在。net中有两种主要的并行方法。旧版本有线程、线程池等。而新的,基于任务,async/await, TPL。根据经验,不要将这两个领域的api混合使用。

其他回答

我和一位同事为此争论了很长时间,他向我证明,在上面的答案目前所显示的范围之外,还有显著的差异。如果你等待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只是开始了一个新的线程来做事情。我仍然没有假装完全理解,但上面的反直觉结果表明,在底层有更多的事情要做,而不仅仅是启动线程来运行代码。

值得一提的是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. 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. It doesn't make so much difference in low memory consumption services (<1GB).

“任务”的名称应该是“延迟”。延迟——因为它不会延迟现有的任务,而是创建一个新的“延迟”任务,另一方面可以等待,并可能导致当前任务主体挂起。它本质上是一个Timer,但没有回调/主体。

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). 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, i.e. it will be out of play for the amount of time asked and won't process any async messages from the queue.

在。net中有两种主要的并行方法。旧版本有线程、线程池等。而新的,基于任务,async/await, TPL。根据经验,不要将这两个领域的api混合使用。

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

任务之间最大的区别。延迟和线程。睡眠就是那个任务。Delay旨在异步运行。使用Task没有意义。同步代码中的延迟。使用线程是一个非常糟糕的主意。在异步代码中休眠。

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

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延迟移到控制台上方。而是写入eline,它将打印运行5.0020168秒。

让我们看看Thread的不同之处。睡眠:

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

试着预测一下它会打印什么…

异步:开始 async:运行0.0070048秒 同步:开始 async:运行5.0119008秒 异步:完成 sync:运行5.0020168秒 同步:完成

另外,有趣的是注意到Thread。睡眠要精确得多,毫秒精度不是真正的问题,而任务。延迟最少需要15-30毫秒。与ms精度相比,这两个函数的开销是最小的(如果你需要更精确的东西,请使用Stopwatch Class)。线程。睡眠仍然束缚着你的线程,任务。在等待的同时,延迟释放它以做其他工作。