回答原来的问题:
当无法选择“一路异步”时,您对处理此类问题有何建议?除了明显的“不要一次产生这么多任务”之外,还有其他东西吗?
绝不是根本原因的解决方案,而是定量的补救措施- 我们可以通过增加将立即创建的线程数量来调整线程池SetMinThreads
(这样比我的设置 1 中的常规“注入率”更快)线程池线程每秒)。它在给定设置中的工作方式很简单。基本上我们在浪费线程池线程,直到池增长到足以开始执行延续。如果我们从足够大的池开始,我们基本上消除了我们只是受人为“注入率”约束的时间段,它试图保持低线程数量(这是有道理的,因为线程池旨在运行受 CPU 限制的任务而不是被阻塞等待异步操作)。
我也应该留下警告信。
默认情况下,最小线程数设置为系统上的处理器数。您可以使用 SetMinThreads 方法来增加最小线程数。但是,不必要地增加这些值可能会导致性能问题。如果同时启动太多任务,所有任务都可能看起来很慢。在大多数情况下,线程池使用自己的分配线程算法会表现得更好。将最小值减少到少于处理器数量也会损害性能。
https://docs.microsoft.com/en-us/dotnet/api/system.threading.threadpool.setminthreads?view=netframework-4.8
还有一个有趣的问题,微软建议在某些情况下增加 ASP.NET 的“最小线程”作为性能/可靠性改进。
https://support.microsoft.com/en-us/help/821268/contention-poor-performance-and-deadlocks-when-you-make-calls-to-web-s
有趣的是,问题中描述的问题并不是纯粹想象的。是真的。它发生在知名且广泛认可的软件中。经验示例——Identity Server 3。
https://github.com/IdentityServer/IdentityServer3.EntityFramework/issues/101
有这个警告的实现(我们必须重写它来解决我们生产场景的问题):
https://github.com/IdentityServer/IdentityServer3.EntityFramework/blob/master/Source/Core.EntityFramework/Serialization/ClientConverter.cs
另一篇文章详细解释了这个问题。
https://blogs.msdn.microsoft.com/vancem/2018/10/16/diagnosing-net-core-threadpool-starvation-with-perfview-why-my-service-is-not-saturating-all-cores-或-似乎停滞不前/
至于单个的奇怪行为,Task.Delay
其中每个新注入的线程池线程都完成了一些异步调用。它似乎是由继续执行内联的方式引起的,Task.Delay
并被Timer
执行。请参阅此调用堆栈,它表明新创建的线程池线程在创建时正在为 .NET 计时器执行一些额外的魔法,然后再处理线程池队列(请参阅 参考资料System.Threading.TimerQueue.AppDomainTimerCallback
)。
在 AsynchronySamples.StrangeTimer.Program.d__2.MoveNext()
在 System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext(对象状态机)
在 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext,ContextCallback 回调,对象状态,布尔值 preserveSyncCtx)
在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback 回调,对象状态,布尔值 preserveSyncCtx)
在 System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()
在 System.Runtime.CompilerServices.AsyncMethodBuilderCore.c__DisplayClass4_0.b__0()
在 System.Runtime.CompilerServices.AsyncMethodBuilderCore.ContinuationWrapper.Invoke()
在 System.Runtime.CompilerServices.TaskAwaiter.c__DisplayClass11_0.b__0()
在 System.Runtime.CompilerServices.AsyncMethodBuilderCore.ContinuationWrapper.Invoke()
在 System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(Action 动作,布尔 allowInlining,Task& currentTask)
在 System.Threading.Tasks.Task.FinishContinuations()
在 System.Threading.Tasks.Task.FinishStageThree()
在 System.Threading.Tasks.Task`1.TrySetResult(TResult 结果)
在 System.Threading.Tasks.Task.DelayPromise.Complete()
在 System.Threading.Tasks.Task.c.b__274_1(对象状态)
在 System.Threading.TimerQueueTimer.CallCallbackInContext(对象状态)
在 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext,ContextCallback 回调,对象状态,布尔值 preserveSyncCtx)
在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback 回调,对象状态,布尔值 preserveSyncCtx)
在 System.Threading.TimerQueueTimer.CallCallback()
在 System.Threading.TimerQueueTimer.Fire()
在 System.Threading.TimerQueue.FireNextTimers()
在 System.Threading.TimerQueue.AppDomainTimerCallback(Int32 id)
[本机到托管转换]
在 kernel32.dll!74e86359()
在 kernel32.dll![下面的帧可能不正确和/或丢失,没有为 kernel32.dll 加载符号]
在 ntdll.dll!77057b74()
在 ntdll.dll!77057b44()