C# 要同步到异步的异步工作具有奇怪的行为
我有一个遗留项目,有很多IoC和C# 要同步到异步的异步工作具有奇怪的行为,c#,.net,asynchronous,async-await,C#,.net,Asynchronous,Async Await,我有一个遗留项目,有很多IoC和HttpClient调用。为了提高性能,我尝试使用TPL来并行化工作。但情况变得更糟了 总之,我们尝试将封装异步方法的同步方法并行化。 重构后,性能更好,但我不理解这种行为 我制作了这个最小的代码示例来重现.NET 4.7控制台项目中的行为: 类程序 { 静态void Main(字符串[]参数) { var tasks=新列表(); 对于(int i=0;iWorkSync(n)); 睡眠(TimeSpan.From毫秒(1)); } Task.WaitAll(t
HttpClient
调用。为了提高性能,我尝试使用TPL来并行化工作。但情况变得更糟了
总之,我们尝试将封装异步方法的同步方法并行化。
重构后,性能更好,但我不理解这种行为
我制作了这个最小的代码示例来重现.NET 4.7控制台项目中的行为:
类程序
{
静态void Main(字符串[]参数)
{
var tasks=新列表();
对于(int i=0;i<15;i++)
{
var n=i;
tasks.Add(Task.Run(()=>WorkSync(n));
睡眠(TimeSpan.From毫秒(1));
}
Task.WaitAll(tasks.ToArray());
}
专用静态void工作同步(int i)
{
Debug.WriteLine($“{i:000}\t{DateTime.Now:HH:mm:ss.fff}\tStartA”);
WorkAsync(i).GetAwaiter().GetResult();
WriteLine($“{i:000}\t{DateTime.Now:HH:mm:ss.fff}\tFinishA”);
}
专用静态异步任务WorkAsync(int i)
{
Debug.WriteLine($“{i:000}\t{DateTime.Now:HH:mm:ss.fff}\tStartB”);
等待任务。运行(()=>Work(i));
Debug.WriteLine($“{i:000}\t{DateTime.Now:HH:mm:ss.fff}\tFinishB”);
}
私人静态无效工作(int i)
{
Debug.WriteLine($“{i:000}\t{DateTime.Now:HH:mm:ss.fff}\tDo某物”);
}
}
结果:
004 11:30:10.629 StartA
000 11:30:10.627 StartA
002 11:30:10.627 StartA
001 11:30:10.627 StartA
003 11:30:10.627 StartA
005 11:30:10.628 StartA
006 11:30:10.628 StartA
007 11:30:10.628 StartA
008 11:30:10.633 StartA
002 11:30:10.692 StartB
001 11:30:10.692 StartB
000 11:30:10.692 StartB
003 11:30:10.692 StartB
005 11:30:10.692 StartB
004 11:30:10.692 StartB
006 11:30:10.695 StartB
007 11:30:10.699 StartB
008 11:30:10.703 StartB
009 11:30:11.632 StartA
009 11:30:11.633 StartB
010 11:30:12.616 StartA
010 11:30:12.617 StartB
011 11:30:13.612 StartA
011 11:30:13.613 StartB
012 11:30:14.612 StartA
012 11:30:14.613 StartB
013 11:30:15.612 StartA
013 11:30:15.613 StartB
014 11:30:16.611 StartA
014 11:30:16.612 StartB
002 11:30:17.612 Do Something
002 11:30:17.614 FinishB
002 11:30:17.615 FinishA
001 11:30:17.615 Do Something
001 11:30:17.657 FinishB
006 11:30:17.658 Do Something
005 11:30:17.636 Do Something
006 11:30:17.680 FinishB
005 11:30:17.701 FinishB
007 11:30:17.723 Do Something
001 11:30:17.658 FinishA
005 11:30:17.744 FinishA
004 11:30:17.744 Do Something
007 11:30:17.765 FinishB
004 11:30:17.808 FinishB
006 11:30:17.723 FinishA
007 11:30:17.830 FinishA
003 11:30:17.894 Do Something
013 11:30:17.786 Do Something
003 11:30:17.895 FinishB
013 11:30:17.917 FinishB
012 11:30:17.919 Do Something
008 11:30:17.830 Do Something
014 11:30:17.788 Do Something
004 11:30:17.851 FinishA
009 11:30:17.851 Do Something
013 11:30:17.922 FinishA
000 11:30:17.872 Do Something
003 11:30:17.918 FinishA
012 11:30:17.927 FinishB
010 11:30:17.922 Do Something
008 11:30:17.931 FinishB
014 11:30:17.933 FinishB
011 11:30:17.955 Do Something
008 11:30:18.046 FinishA
009 11:30:17.958 FinishB
009 11:30:18.111 FinishA
014 11:30:18.068 FinishA
000 11:30:17.980 FinishB
000 11:30:18.114 FinishA
010 11:30:18.024 FinishB
011 11:30:18.089 FinishB
012 11:30:18.003 FinishA
011 11:30:18.138 FinishA
010 11:30:18.116 FinishA
Work
方法仅在主系统中启动所有任务后执行。
我已使用调试器对此进行了检查,阻塞的不是调试显示
1) 我不明白这个日程安排。
你能解释一下原因吗
2) 前10项任务开始得很快,但后5项任务开始得很慢。
你能解释一下原因吗
我不明白这个日程安排。你能解释一下原因吗
你有一个紧密的循环来启动一堆任务。它们都启动了,但每个都启动了另一个线程。直到启动线程调用Debug.WriteLine($“{i:000}\tFinishB”)代码>
Work()
线程被阻塞的一个原因是Debug.WriteLine()
获取了一个锁-因此,如果其他线程当前正在写入以调试Work()
线程将被阻塞。其寓意是,Debug.WriteLine()
可以改变多线程的行为,因为它使用锁
前10项任务的启动速度非常快,但最后5项任务的启动速度非常慢。你能解释一下原因吗 然而,发生这种情况还有另一个更重要的原因:线程池的“最小线程限制” 线程池保持等待运行的最小线程数。您可以通过以下代码查看该值:
ThreadPool.GetMinThreads(out int workers, out int ports);
Console.WriteLine(workers); // Prints 8 on my system.
现在需要知道的重要一点是,如果需要的线程数量超过了最小数量,那么只有在延迟几百毫秒后才会创建新的线程(不确定具体需要多长时间,但似乎大约需要一秒钟)
因此,除了在Debug.WriteLine()
实现中由锁引起的阻塞之外,还发生了以下情况:
- 启动了大量任务,消耗的线程池大小超过了最小线程池大小,因此在启动前几个任务后,新任务之间会引入延迟
- 由于这种延迟,当开始执行
任务时,它就被延迟了。这导致它比其他情况下启动得晚得多Work()
ThreadPool.SetMinThreads(100, 100);
当我尝试这样做时,所有任务都会更快地启动,并且一些“Do Something”消息会在所有其他任务启动之前出现(而您注意到,以前这些消息只会在所有其他任务启动之后出现)
注意微软:
您可以使用ThreadPool.SetMinThreads方法增加空闲线程的最小数量。但是,不必要地增加这些值可能会导致性能问题。如果同时启动的任务太多,则所有任务都可能看起来很慢。在大多数情况下,线程池使用其自己的线程分配算法将执行得更好
这里需要注意的是:控制台的输出不一定遵循异步代码执行的顺序或时间。您可能最终会追逐一条红鲱鱼……
Work
方法会在最后执行,因为您会同时启动所有任务,这将创建多个线程,这些线程将并行运行。out没什么奇怪的吗?你想完成什么?@TheodorZoulias,我编辑了这个问题以输出时间信息。@或者我也会同时调用HttpClient,以对同一服务执行1000多个请求,每次5个。我不使用Task.RunAnywhere,也不使用类似这样的任务循环。为此,在.NET旧版中,我必须将servicePointManager.DefaultConnectionLimit
增加到默认值2以上。如果我替换Debug.WriteLine
call byConcurrentBag.Add
并显示包,则我还可以使用ActionBlock之类的类使编码和可控节流变得更加容易。你说得对,这不是调度问题,而是资源分配问题。