等待任務完成時,是什么導致執行時間出現這些離散峰值?為什么使用 WhenAll 比快速回圈并檢查所有任務是否完成要慢?這是一個簡化的示例,但創建是因為我們在呼叫 WhenAll 時看到了看似不必要的延遲。NativeAOT 似乎沒有那么有效,所以也許是一些不必要的 JITing?

using System.Diagnostics;
namespace ConsoleApp1
{
internal class Program
{
static async Task Main()
{
for (int i = 1; i <= 100; i =1)
{
await RunTest(i);
}
}
public static async Task RunTest(int count)
{
var sw = Stopwatch.StartNew();
var tasks = new List<Task>();
// Construct started tasks
for (int i = 0; i < count; i )
{
tasks.Add(Task.Run(() => Thread.Sleep(250)));
}
// Test 1, WhenAll
//await Task.WhenAll(tasks);
// Test 2, 10ms loop
bool completed = false;
while (!completed)
{
await Task.Delay(10);
completed = tasks.All(t => t.IsCompleted);
}
Console.WriteLine($"{count},{sw.Elapsed.TotalSeconds}");
}
}
}
上面的資料都是從命令列運行一個獨立的 exe 收集的。但是當在 VS 中運行時,它似乎不是垃圾收集問題,我懷疑這是因為 VS 診斷程式沒有顯示任何垃圾收集標記。

uj5u.com熱心網友回復:
Thread.Sleep是阻塞操作。您正在推動在執行緒池上執行大量作業,但每個執行緒都坐了 250 毫秒無所事事。IIRC,您的執行緒池從基于您的機器擁有的內核數量的執行緒總數開始,但不要參考我的話。如果為了爭論你有一臺 4 核機器,你的池只有 4 個執行緒。您正在推動在這 4 個執行緒上執行數百項作業,但隨后您阻止了它們。運行時看到這種壓力在增加,正如@Theodor Zoulias 所說,正在以每秒 1 個的速度創建更多執行緒并將其添加到池中,這并不多。如果你改變
tasks.Add(Task.Run(() => Thread.Sleep(250)));
至
tasks.Add(Task.Run(() => Task.Delay(250)));
或者更好
tasks.Add(Task.Delay(250));
你幾乎肯定會看到你的問題消失了。
至于為什么Task.WhenAll慢,你可以看看它的來源,它做了很多作業,它不只是檢查一個簡單的屬性。
uj5u.com熱心網友回復:
其他答案涵蓋了執行緒池饑餓。但是為什么Task.Delay/Task.WhenAll圖表看起來不同?
每次您打電話時,RunTest您都會添加另一個任務。如果所有任務都可以并行運行,RunTest將在 250ms 內回傳。如果他們不能,那么計劃任務將分批執行并RunTest回傳n * 250ms. 由于正如其他人所指出的那樣,執行緒池每秒只會添加一個新執行緒,因此您會期望結果在n ~= 4.
當任務完成時,任何延續都將立即在同一個執行緒上運行。Task.WhenAll為每個任務添加一個延續,當每個任務完成時遞減一個計數器。在最后一項任務中,它將收集結果并完成自己的任務。所以它不會給執行緒池增加任何壓力。
但是,每 10 毫秒運行一個額外的任務確實會增加執行緒池的壓力。這個額外的任務將確保執行緒池被認為是忙碌的,即使有其他k-1執行緒忙碌。更有可能每秒創建一個新執行緒。稍微改變行為。但是,當執行緒池繁忙時,定時器很可能只在一批任務完成時或在最后一批任務期間才會執行。
uj5u.com熱心網友回復:
顯然你正在觀察飽和的效果ThreadPool。當ThreadPool飽和時,它的行為是通過以每秒一個新執行緒的速率產生新執行緒來滿足需求1。似乎Task.WhenAll受饑餓的影響比拉技術更嚴重while (!completed),原因我老實說無法詳細解釋。
道德教訓是,飽和ThreadPool是一種糟糕的情況,應該避免。如果您的應用程式對執行緒、執行緒和更多執行緒有著無法滿足的需求,您可以考慮為每個LongRunning操作創建專用執行緒,而不是從ThreadPool. 它ThreadPool旨在作為一個小型可重用執行緒池,以幫助分攤運行頻繁和輕量級操作(如回呼、延續、事件處理程式等)的成本。
1這是 .NET 6 的行為,沒有記錄。可以通過實驗觀察到,但在未來的 .NET 版本中可能會發生變化。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/491149.html
