Task.Delay()发生了什么.等待()?

Rek*_*ino 11 .net c# multithreading task task-parallel-library

我很困惑,为什么Task.Delay().Wait()需要4倍更长的时间,然后Thread.Sleep()

例如,task-00 仅在第9个线程上运行并占用了2193ms?我知道,同步等待在任务中很糟糕,因为整个线程被阻止了.它只是为了测试.

控制台应用中的简单测试:

bool flag = true;
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
    var cntr = i;
    {
        var start = sw.ElapsedMilliseconds;
        var wait = flag ? 100 : 300;
        flag = !flag;

        Task.Run(() =>
        {
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t START: {start}ms");                     
            //Thread.Sleep(wait);
            Task.Delay(wait).Wait();
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t END: {sw.ElapsedMilliseconds}ms");
            ;
        });
    }
}
Console.ReadKey();
return;
Run Code Online (Sandbox Code Playgroud)

使用Task.Delay().Wait():
task-03 ThrID:05,Wait = 300ms,START:184ms
task-04 ThrID:07,Wait = 100ms,START:184ms
task-00 ThrID:09,Wait = 100ms,START:0ms
task-06 ThrID:04 ,Wait = 100ms,START:185ms
task-01 ThrID:08,Wait = 300ms,START:
183ms task-05 ThrID:03,Wait = 300ms,START:185ms
task-02 ThrID:06,Wait = 100ms,START:184ms
任务-07 ThrID:10,Wait = 300ms,START:209ms
task-07 ThrID:10,Wait = 300ms,END:1189ms
task-08 ThrID:12,Wait = 100ms,START:226ms
task-09 Thrid:10,Wait = 300ms,START:226ms
task-09 ThrID:10,Wait = 300ms,END:2192ms
task-06 ThrID:04,Wait = 100ms,END:2193ms
task-08 ThrID:12,Wait = 100ms,END:2194ms
task- 05 ThrID:03,Wait = 300ms,END:2193ms
task-03 ThrID:05,Wait = 300ms,END:2193ms
task-00 ThrID:09,Wait = 100ms,END:2193ms
task-02 ThrID:06,Wait = 100ms ,END:2193ms
task-04 ThrID:07,Wait = 100ms,END:2193ms
task-01 ThrID:08,Wait = 300ms,END:2193ms

使用Thread.Sleep():
task-00 ThrID:03,Wait = 100ms,START:0ms
task-03 ThrID:09,Wait = 300ms,START:179ms
task-02 ThrID:06,Wait = 100ms,START:178ms
task-04 ThrID:08 ,Wait = 100ms,START:179ms
task-05 ThrID:04,Wait = 300ms,START:179ms
task-06 ThrID:07,Wait = 100ms,START:184ms
task-01 ThrID:05,Wait = 300ms,START:178ms
任务-07 ThrID:10,Wait = 300ms,START:184ms
task-00 ThrID:03,Wait = 100ms,END:284ms
task-08 ThrID:03,Wait = 100ms,START:184ms
task-02 ThrID:06,Wait = 100ms,END:285ms
task-09 ThrID:06,Wait = 300ms,START:184ms
task-04 ThrID:08,Wait = 100ms,END:286ms
task-06 ThrID:07,Wait = 100ms,END:293ms
task- 08 ThrID:03,Wait = 100ms,END:385ms
task-03 ThrID:09,Wait = 300ms,END:485ms
task-05 ThrID:04,Wait = 300ms,END:486ms
task-01 ThrID:05,Wait = 300ms ,结束:493ms
任务-07 ThrID:10,等待= 300ms,结束:494ms
任务-09 Thrid:06,等待= 300ms,结束:586ms

编辑:
asynclambda await Task.Delay()一样快Thread.Sleep(),也可能更快(511ms).
编辑2:
使用与循环中的10次迭代ThreadPool.SetMinThreads(16, 16); Task.Delay().Wait()一样快的工作Thread.Sleep.随着迭代次数的增加,它再次变慢.同样有趣的是,如果不进行调整,我将迭代次数Thread.Sleep增加到30,它仍然更快,然后使用编辑3进行10次迭代Task.Delay().Wait()
:
重载的Task.Delay(wait).Wait(wait)工作速度与Thread.Sleep()

Han*_*ant 7

我重新编写了已发布的片段,以便更好地订购结果,我全新的笔记本电脑有太多内核,无法很好地解释现有的混乱输出.记录每个任务的开始和结束时间,并在完成所有任务后显示它们.并记录任务的实际开始时间.我有:

0: 68 - 5031
1: 69 - 5031
2: 68 - 5031
3: 69 - 5031
4: 69 - 1032
5: 68 - 5031
6: 68 - 5031
7: 69 - 5031
8: 1033 - 5031
9: 1033 - 2032
10: 2032 - 5031
11: 2032 - 3030
12: 3030 - 5031
13: 3030 - 4029
14: 4030 - 5031
15: 4030 - 5031
Run Code Online (Sandbox Code Playgroud)

啊,这突然变得很有意义.处理线程池线程时始终需要注意的模式.请注意每秒一次重要事件是如何发生并且两个tp线程开始运行并且其中一些可以完成.

这是一个死锁场景,类似于这个Q + A,但没有该用户代码的更灾难性的结果.原因几乎是不可能看到的,因为它隐藏在.NETFramework代码中,你必须看看如何实现Task.Delay()来理解它.

相关代码在这里,请注意它如何使用System.Threading.Timer来实现延迟.关于该计时器的细节是它的回调是在线程池上执行的.这是Task.Delay()可以实现"你不为你不使用的东西付款"的承诺的基本机制.

粗略的细节是,如果线程池忙于转动线程池执行请求,这可能需要一段时间.这不是计时器很慢,问题是回调方法不会很快就开始.这个程序中的问题,Task.Run()添加了一堆请求,可以同时执行.发生死锁是因为在执行计时器回调之前,Task.Run()启动的tp线程无法完成Wait()调用.

你可以通过将这段代码添加到Main()的开头来使它成为永久挂起程序的硬死锁:

     ThreadPool.SetMaxThreads(Environment.ProcessorCount, 1000);
Run Code Online (Sandbox Code Playgroud)

但正常的最大线程要高得多.线程池管理器利用它来解决这种死锁问题.每秒一次,当现有的线程没有完成时,它允许执行两个以上的线程而不是"理想"数量的线程.这就是你在输出中看到的内容.但它一次只有两个,不足以在Wait()调用中阻塞的8个繁忙线程中造成很大影响.

Thread.Sleep()调用没有这个问题,它不依赖于.NETFramework代码或线程池来完成.操作系统线程调度程序负责处理它,它始终通过时钟中断运行.因此允许新的tp线程每100或300毫秒开始执行而不是每秒一次.

很难给出具体建议以避免这种死锁陷阱.除了通用建议之外,始终避免让工作线程阻塞.


Nic*_*ick 5

无论Thread.Sleep(),也不Task.Delay()保证内部将是正确的。

Thread.Sleep()工作方式Task.Delay()大不相同。 Thread.Sleep()阻止当前线程并阻止其执行任何代码。 Task.Delay()创建一个计时器,该计时器将在时间到期时进行计时,并将其分配给线程池执行。

您可以使用来运行代码Task.Run(),它将创建任务并将其排队在线程池中。使用时Task.Delay(),当前线程将释放回线程池中,并且可以开始处理其他任务。这样,多个任务将更快地启动,并且您将记录所有任务的启动时间。然后,当延迟计时器开始计时时,它们也耗尽了池,完成某些任务所花的时间比开始时要长。这就是为什么您要长时间录制的原因。

使用时Thread.Sleep(),您将阻止池中的当前线程,并且该线程无法处理更多任务。线程池不会立即增长,因此只需等待新任务。因此,所有任务大约同时运行,这对您来说似乎更快。

编辑:您使用Task.Wait()。在您的情况下,Task.Wait()尝试内联同一线程上的执行。同时,它Task.Delay()依赖在线程池上执行的计时器。一旦调用Task.Wait()从池中阻止一个工作线程,第二次您就需要池中有一个可用线程来完成相同工作方法的操作。当您await使用时Delay(),不需要此类内联,并且工作线程可立即用于处理计时器事件。当您使用时Thread.Sleep,您没有计时器来完成worker方法。

我相信这是造成延误的巨大差异的原因。