2014-11-03 59 views
3

假設我想大致N次啓動每秒平均分配任務。使用await Task.Delay中的殺死性能

所以,我想這一點:

public async Task Generate(int numberOfCallsPerSecond) 
{ 
    var delay = TimeSpan.FromMiliseconds(1000/numberOfCallsPerSecond); // a call should happen every 1000/numberOfCallsPerSecond miliseconds 
    for (int i=0; i < numberOfcallsPerSecond; i++) 
    { 
     Task t = Call(); // don't wait for result here 
     await Task.Delay(delay); 
    } 
} 

起初我預計這1秒鐘內運行,但對於numberOfCallsPerSecond = 100它在我的12核CPU需要16 seconds。 似乎是等待Task.Delay增加了很多的開銷(當然沒有它的地方生成的通話發生在3ms。

我沒想到在這種情況下,等待會增加很多開銷。 ?正常

編輯:

請忘了()的調用運行該代碼顯示similiar結果:

public async Task Generate(int numberOfCallsPerSecond) 
{ 
var delay = TimeSpan.FromMiliseconds(1000/numberOfCallsPerSecond); // a call should happen every 1000/numberOfCallsPerSecond miliseconds 
for (int i=0; i < numberOfcallsPerSecond; i++) 
{ 
    await Task.Delay(delay); 
} 
} 

我試圖用numberOfCallsPerSecond = 500運行它,它需要大約10秒,我的預期Generate取大約1秒,而不是10倍以上

+0

你可以給Call方法一些代碼嗎?你確定這個方法在返回任務對象之前不需要時間嗎? – Fabske 2014-11-03 14:28:14

+0

您在10秒鐘內的500個電話的新結果與調度程序量相當一致。 – 2014-11-03 14:41:47

+0

嗨,大家好,請忘記Call()方法,查看我的EDIT – 2014-11-03 14:42:10

回答

12

Task.Delay重量輕但不準確。由於沒有延遲的循環完成得更快,這聽起來像你的線程空閒,並使用OS睡眠等待定時器過去。定時器根據操作系統線程調度量(在執行線程預佔的同一中斷處理程序中)進行檢查,默認爲16ms。

您可以減少timeBeginPeriod的量程,但是如果您需要速率限制而不是精確定時,則更好(更省電)的方法是跟蹤經過的時間(Stopwatch類適用於此)和呼叫數只有在通話時間趕上之後纔會延遲。總體效果是,你的線程將每秒喚醒〜60次,每次啓動一些工作項目。如果你的CPU變得忙於其他事情,當你得到控制權時,你會開始額外的工作項目 - 儘管如果這是你需要的,那麼限制一次啓動的項目數量也是非常簡單的。

public async Task Generate(int numberOfCallsPerSecond) 
{ 
    var elapsed = Stopwatch.StartNew(); 
    var delay = TimeSpan.FromMiliseconds(1000/numberOfCallsPerSecond); // a call should happen every 1000/numberOfCallsPerSecond miliseconds 
    for (int i=0; i < numberOfcallsPerSecond; i++) 
    { 
     Call(); // don't wait for result here 
     int expectedI = elapsed.Elapsed.TotalSeconds * numberOfCallsPerSecond; 
     if (i > expectedI) await Task.Delay(delay); 
    } 
} 
+0

嘿,很好的解釋它的答案:)。我真的很驚訝的開銷。試試你的解決方案。 +1 – 2014-11-03 14:51:59

+2

'await Task.Delay()'不會導致線程在給定的時間內休眠。相反,它使用一個計時器並在計時器過去時安排延續執行。所以重要的是計時器的解析度,而不是睡眠的解析度。雖然默認情況下都是16毫秒,這就是觀察到的行爲與您所描述的行爲相同的原因。 – svick 2014-11-05 12:58:17

+0

@svick:我沒有說'Task.Delay()'睡眠線程。如果沒有任務準備就緒,則任務分派循環。 – 2014-11-05 13:16:48

1

我的心理調試說,你Call方法有哪些需要時間同步執行顯著同步部分(即一個await之前的部分)。

如果你想Generate方法僅適用於「火」起來,這些Call電話,並讓它們同時運行(包括同步部分),你需要使用Task.Run他們卸載到一個ThreadPool線程:

var task = Task.Run(() => Call()); 
await Task.Delay(delay); 

Task.Delay幾乎沒有增加開銷。它在內部使用System.Threading.Timer,只需要很少的資源。

+0

嘿,謝謝你的回答,請你檢查一下我的編輯? – 2014-11-03 14:41:42

+0

問題的第一次修改提到「沒有'Task.Delay'到位代碼在3ms中完成」似乎排除了這個解釋 – 2014-11-03 14:49:32

0

如果您使用Task.Delay()的時間跨度,它會殺死CPU。使用一個整數,它不會。真實的故事。不知道爲什麼。

+0

你可以發佈一些代碼/基準嗎?將是一個很好的補充。 – FRoZeN 2017-01-31 10:18:53

+0

當我找到時間時,我會盡量記得把它們放在一起。這是我去年在寫日誌Tailer時發現的東西。 – Sinaesthetic 2017-02-01 16:53:05