Task.Delay().Wait()发生了什么? [英] What is going on with Task.Delay().Wait()?

查看:239
本文介绍了Task.Delay().Wait()发生了什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很困惑为什么Task.Delay().Wait()要花费 4倍的时间,然后是Thread.Sleep()?

I'm confused why Task.Delay().Wait() takes 4x more time, then Thread.Sleep()?

例如 task-00 仅线程9 上运行,并花费了 2193ms ? 我知道,同步等待在任务中是不好的,因为整个线程都被阻塞了.只是为了测试.

E.g. task-00 was running on only thread 9 and took 2193ms? I'm aware, that sync wait is bad in tasks, because the whole thread being blocked. It is just for test.

在控制台应用程序中进行简单测试:

Simple test in console application:

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;

使用Task.Delay().Wait():
task-03 ThrID:05,Wait = 300ms,START:184ms
任务04阈值ID:07,等待= 100毫秒,开始:184毫秒
task-00 ThrID:09,Wait = 100ms,START:0ms
任务06 ThrID:04,等待= 100毫秒,开始:185毫秒
task-01 ThrID:08,Wait = 300ms,START:183ms
任务05阈值ID:03,等待= 300ms,开始:185ms
任务02 ThrID:06,等待= 100毫秒,开始:184毫秒
task-07 ThrID:10,Wait = 300ms,START:209ms
任务07 ThrID:10,等待= 300ms,END:1189ms
task-08 ThrID:12,等待时间= 100毫秒,START:226毫秒
任务09阈值ID:10,等待= 300ms,开始:226ms
任务09阈值ID:10,等待= 300ms,结束:2192ms
task-06 ThrID:04,Wait = 100ms,END:2193ms
task-08 ThrID:12,Wait = 100ms,END:2194ms
任务05阈值ID:03,等待= 300ms,结束:2193ms
task-03 ThrID:05,Wait = 300ms,END:2193ms
task-00 ThrID:09,Wait = 100ms,END:2193ms
task-02 ThrID:06,等待= 100ms,END:2193ms
任务04阈值ID:07,等待= 100ms,结束:2193ms
task-01 ThrID:08,Wait = 300ms,END:2193ms

With 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
task-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
任务03 ThrID:09,等待= 300ms,开始:179ms
任务02 ThrID:06,等待= 100ms,开始:178ms
任务04阈值ID:08,等待= 100ms,开始:179ms
任务05阈值ID:04,等待= 300ms,开始:179ms
Task-06 ThrID:07,Wait = 100ms,START:184ms
任务01阈值ID:05,等待= 300ms,开始:178ms
task-07 ThrID:10,Wait = 300ms,START:184ms
task-00 ThrID:03,Wait = 100ms,END:284ms
task-08 ThrID:03,Wait = 100ms,START:184ms
任务02 ThrID:06,等待= 100ms,结束:285ms
任务09阈值ID:06,等待= 300ms,开始:184ms
task-04 ThrID:08,Wait = 100ms,END:286ms
任务06 ThrID:07,等待= 100ms,结束:293ms
task-08 ThrID:03,Wait = 100ms,END:385ms
任务03 ThrID:09,等待= 300ms,结束:485ms
任务05阈值ID:04,等待= 300ms,结束:486ms
task-01 ThrID:05,Wait = 300ms,END:493ms
任务07 ThrID:10,等待= 300ms,结束:494ms
task-09 ThrID:06,Wait = 300ms,END:586ms

With 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
task-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, END: 493ms
task-07 ThrID: 10, Wait=300ms, END: 494ms
task-09 ThrID: 06, Wait=300ms, END: 586ms

修改:
使用async lambda和await Task.Delay()Thread.Sleep()一样快,可能也更快(511ms).

在循环中进行10次迭代时,使用ThreadPool.SetMinThreads(16, 16);Task.Delay().Wait()Thread.Sleep一样快.随着迭代次数的增加,它又变慢了.有趣的是,如果不进行调整,我将Thread.Sleep的迭代次数增加到 30 ,它仍然会更快,然后使用Task.Delay().Wait()
进行 10 迭代.
重载Task.Delay(wait).Wait(wait)的速度与Thread.Sleep()


With async lambda and await Task.Delay() is as fast as Thread.Sleep(), may be also faster (511ms).
Edit 2:
With ThreadPool.SetMinThreads(16, 16); Task.Delay().Wait() works as fast as Thread.Sleep for 10 iteration in the loop. With more iterations it's slower again. It's also interesting, that if without adjusting I increase the number of iterations for Thread.Sleep to 30, it's still faster, then 10 iteration with Task.Delay().Wait()
Edit 3:
The overloading Task.Delay(wait).Wait(wait) works as fast as Thread.Sleep()

推荐答案

我稍微重写了一下代码片段以更好地整理结果,但我的全新笔记本电脑的内核太多,无法很好地解释现有的混乱输出.记录每个任务的开始和结束时间,并在完成所有任务后显示它们.并记录任务的实际开始时间.我知道了:

I rewrote the posted snippet a bit to get the results ordered better, my brand-new laptop has too many cores to interpret the existing jumbled output well enough. Recording the start and end times of each task and displaying them after they are all done. And recording the actual start time of the Task. I got:

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

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

Ah, that suddenly makes a lot of sense. A pattern to always watch for when dealing with threadpool threads. Note how once a second something significant happens and two tp threads start running and some of them can complete.

这是一个僵局,类似于

This is a deadlock scenario, similar to this Q+A but otherwise without the more disastrous outcome of that user's code. The cause is next-to-impossible to see since it is buried in .NETFramework code, you'd have to look how Task.Delay() is implemented to make sense of it.

相关代码在此处,请注意它如何使用System.Threading.Timer来实现延迟.关于该计时器的一个棘手的细节是它的回调是在线程池上执行的.这是Task.Delay()可以实现您不为不使用的商品付费"承诺的基本机制.

The relevant code is here, note how it uses a System.Threading.Timer to implement the delay. A gritty detail about that timer is that its callback is executed on the threadpool. Which is the basic mechanism by which Task.Delay() can implement the "you don't pay for what you don't use" promise.

棘手的细节是,如果线程池忙于处理线程池执行请求,则可能要花一些时间.这不是计时器很慢,问题在于回调方法还没有足够快地启动.该程序中的问题Task.Run()添加了一堆请求,超过了可以同时执行的请求数量.之所以会发生死锁,是因为由Task.Run()启动的tp线程在计时器回调执行之前无法完成Wait()调用.

The gritty detail is that this can take a while if the threadpool is busy churning away at threadpool execution requests. It's not the timer is slow, the problem is that the callback method just doesn't get started soon enough. The problem in this program, Task.Run() added a bunch of requests, more than can be executed at the same time. The deadlock occurs because the tp-thread that was started by Task.Run() cannot complete the Wait() call until the timer callback executes.

通过将以下代码添加到Main()的开头,可以使它永久死机,使其永久挂起程序:

You can make it a hard deadlock that hangs the program forever by adding this bit of code to the start of Main():

     ThreadPool.SetMaxThreads(Environment.ProcessorCount, 1000);

但是正常的最大线程数要高得多.线程池管理器利用它来解决这种死锁.当现有线程未完成时,它每秒执行一次的线程数将比理想"数量多两个.这就是您在输出中看到的内容.但这一次只有两个,不足以在Wait()调用中阻塞的8个繁忙线程中占用很多凹痕.

But the normal max-threads is much higher. Which the threadpool manager takes advantage of to solve this kind of deadlock. Once a second it allows two more threads than the "ideal" number of them to execute when the existing ones don't complete. That's what you see back in the output. But it is only two at a time, not enough to put much of a dent in the 8 busy threads that are blocked on the Wait() call.

Thread.Sleep()调用不存在此问题,它不依赖于.NETFramework代码或线程池来完成.它是OS线程调度程序来处理的,并且它总是通过时钟中断来运行.因此,允许新的tp线程开始每100或300毫秒执行一次,而不是每秒执行一次.

The Thread.Sleep() call does not have this problem, it doesn't depend on .NETFramework code or the threadpool to complete. It is the OS thread scheduler that takes care of it, and it always runs by virtue of the clock interrupt. Thus allowing new tp threads to start executing every 100 or 300 msec instead of once a second.

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

Hard to give concrete advice to avoid such a deadlock trap. Other than the universal advice, always avoid having worker threads block.

这篇关于Task.Delay().Wait()发生了什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆