c# - Task.Delay().Wait() 是怎么回事?

标签 c# .net multithreading task task-parallel-library

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

例如task-00 是否在仅线程 9 上运行并花费了 2193 毫秒? 我知道,同步等待在任务中很糟糕,因为整个线程都被阻塞了。仅供测试。

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

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():
任务 03 ThrID:05,等待 = 300 毫秒,开始:184 毫秒
任务 04 ThrID:07,等待 = 100 毫秒,开始:184 毫秒
task-00 ThrID: 09, Wait=100ms, START: 0ms
任务 06 ThrID:04,等待 = 100 毫秒,开始:185 毫秒
任务 01 ThrID:08,等待 = 300 毫秒,开始:183 毫秒
任务 05 ThrID:03,等待 = 300 毫秒,开始:185 毫秒
任务 02 ThrID:06,等待 = 100 毫秒,开始:184 毫秒
任务 07 ThrID:10,等待 = 300 毫秒,开始:209 毫秒
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
任务 05 ThrID:03,等待 = 300 毫秒,结束:2193 毫秒
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,等待 = 300 毫秒,开始:179 毫秒
任务 02 ThrID:06,等待 = 100 毫秒,开始:178 毫秒
任务 04 ThrID:08,等待 = 100 毫秒,开始:179 毫秒
任务 05 ThrID:04,等待 = 300 毫秒,开始:179 毫秒
任务 06 ThrID:07,等待 = 100 毫秒,开始:184 毫秒
任务 01 ThrID:05,等待 = 300 毫秒,开始:178 毫秒
任务 07 ThrID:10,等待 = 300 毫秒,开始:184 毫秒
task-00 ThrID: 03, Wait=100ms, END: 284ms
任务 08 ThrID:03,等待 = 100 毫秒,开始:184 毫秒
task-02 ThrID: 06, Wait=100ms, END: 285ms
任务 09 ThrID:06,等待 = 300 毫秒,开始:184 毫秒
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
任务 01 ThrID:05,等待 = 300 毫秒,结束:493 毫秒
task-07 ThrID: 10, Wait=300ms, END: 494ms
task-09 ThrID: 06, Wait=300ms, END: 586ms

编辑:
使用 async lambda 和 await Task.Delay()Thread.Sleep() 一样快,可能是也更快(511 毫秒)。
编辑 2:
使用 ThreadPool.SetMinThreads(16, 16); Task.Delay().Wait()Thread.Sleep 一样快 10 次迭代在循环。随着更多的迭代,它又变慢了。这也很有趣,如果不调整我将 Thread.Sleep 的迭代次数增加到 30,它仍然更快,然后 10 迭代Task.Delay().Wait()
编辑 3:
重载 Task.Delay(wait).Wait(wait) 的工作速度与 Thread.Sleep()

一样快

最佳答案

我稍微重写了发布的代码片段,以便更好地排序结果,我全新的笔记本电脑有太多内核,无法很好地解释现有的困惑输出。记录每项任务的开始和结束时间,并在全部完成后显示。并记录Task的实际开始时间。我得到了:

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 线程开始运行,其中一些可以完成。

这是一个死锁场景,类似于this Q+A但除此之外,该用户的代码不会造成更灾难性的后果。原因几乎是不可能的,因为它隐藏在 .NETFramework 代码中,您必须查看 Task.Delay() 的实现方式才能理解它。

相关代码is here ,请注意它如何使用 System.Threading.Timer 来实现延迟。关于该计时器的一个重要细节是它的回调是在线程池上执行的。这是 Task.Delay() 可以实现“你不用为你不用的东西付费” promise 的基 native 制。

具体细节是,如果线程池正忙于处理线程池执行请求,这可能需要一段时间。不是计时器慢,问题是回调方法启动不够快。这个程序中的问题,Task.Run() 增加了一堆请求,超过了可以同时执行的请求。死锁的发生是因为由 Task.Run() 启动的 tp 线程在计时器回调执行之前无法完成 Wait() 调用。

您可以通过将这段代码添加到 Main() 的开头来使其成为一个使程序永远挂起的硬死锁:

     ThreadPool.SetMaxThreads(Environment.ProcessorCount, 1000);

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

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

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

关于c# - Task.Delay().Wait() 是怎么回事?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53648014/

相关文章:

c# - 使用 XmlInclude 或 SoapInclude 属性指定静态未知的类型

Python 在多线程中 fork

java - 如何在新线程中创建数组的副本

c# - 从 MemoryStream 读取数据时 SoundPlayer 崩溃

c# - 在不创建 100 个表的情况下存储来自表单的数据 : ASP. NET 和 SQL Server

c# - 如何在 .NET 中查找 windows 拨号规则

c# - 使用 LINQ to SQL 创建查找时出现 ForeignKeyReferenceAlreadyHasValueException

python - python 中的多线程 I/O 减慢

c# - 对象初始值设定项在 List<T> 中不起作用

c# - BigInteger 解析八进制字符串?