[英]await Task.Delay(foo); takes seconds instead of ms
與類似IO的操作結合使用時,在Task.Delay
使用可變延遲Task.Delay
隨機花費幾秒鍾而不是毫秒。
復制代碼:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication {
class Program {
static void Main(string[] args) {
Task[] wait = {
new delayTest().looper(5250, 20),
new delayTest().looper(3500, 30),
new delayTest().looper(2625, 40),
new delayTest().looper(2100, 50)
};
Task.WaitAll(wait);
Console.WriteLine("All Done");
Console.ReadLine();
}
}
class delayTest {
private Stopwatch sw = new Stopwatch();
public delayTest() {
sw.Start();
}
public async Task looper(int count, int delay) {
var start = sw.Elapsed;
Console.WriteLine("Start ({0}, {1})", count, delay);
for (int i = 0; i < count; i++) {
var before = sw.Elapsed;
var totalDelay = TimeSpan.FromMilliseconds(i * delay) + start;
double wait = (totalDelay - sw.Elapsed).TotalMilliseconds;
if (wait > 0) {
await Task.Delay((int)wait);
SpinWait.SpinUntil(() => false, 1);
}
var finalDelay = (sw.Elapsed - before).TotalMilliseconds;
if (finalDelay > 30 + delay) {
Console.WriteLine("Slow ({0}, {1}): {4} Expected {2:0.0}ms got {3:0.0}ms", count, delay, wait, finalDelay, i);
}
}
Console.WriteLine("Done ({0}, {1})", count, delay);
}
}
}
還報告了關於連接 。
我正在運行一個任務,該任務從網絡流中讀取,然后延遲20ms,然后再次讀取(執行500次讀取,這大約需要10秒鍾)。 當我僅閱讀1個任務時,此方法效果很好,但是當我運行多個任務時,有些事情會延遲很長(60秒),這會發生奇怪的事情。 我的ms延遲任務突然掛了一半。
我正在運行以下代碼 (簡體):
var sw = Stopwatch();
sw.Start()
await Task.Delay(20); // actually delay is 10, 20, 30 or 40;
if (sw.Elapsed.TotalSeconds > 1) {
Console.WriteLine("Sleep: {0:0.00}s", sw.Elapsed.TotalSeconds);
}
打印:
睡眠:11.87秒
(實際上,它使20ms的延遲達到了99%的時間,這些被忽略了)。
此延遲幾乎比預期的時間長600倍。 同一延遲同時發生在3個不同的線程上,它們也同時在同一時間再次繼續。
簡短任務完成后,60秒的睡眠任務會在大約40秒后正常喚醒。
一半時間甚至沒有發生此問題。 另一半的延遲時間為11.5-12秒。 我會懷疑調度或線程池問題,但是所有線程都應該是空閑的。
當我在卡住階段暫停程序時,主線程stacktrace位於Task.WaitAll
,在await Task.Delay(20)
上調度了3個任務,在await Task.Delay(60000)
上調度了一個任務。 另外還有4個任務正在等待前四個任務,報告類似“任務24”的事件正在此對象上等待:“任務5313”(線程0擁有)。 所有4個任務都說等待任務歸線程0所有。還有4個ContinueWith任務,我認為我可以忽略。
還有其他事情在進行,例如第二個控制台應用程序寫入網絡流,但是一個控制台應用程序不應影響另一個。
我對此一無所知。 到底是怎么回事?
更新:
根據評論和問題:
當我運行4次程序時,它將掛起2-3次,持續10-15秒,然后將其正常運行1-2次(並且不會顯示“睡眠:{0:0.00} s”。)
Thread.Count
確實增加了,但是無論掛起如何,這種情況都會發生。 我只是在沒有掛起的情況下運行,然后Thread.Count
從24開始,在一秒鍾后達到40,大約22秒,短任務正常完成,然后Thread.Count
在接下來的40里緩慢下降到22。秒。
在下面的鏈接中找到更多代碼,完整代碼。 起始客戶:
List<Task> tasks = new List<Task>();
private void makeClient(int delay, int startDelay) {
Task task = new ClientConnection(this, delay, startDelay).connectAsync();
task.ContinueWith(_ => {
lock (tasks) { tasks.Remove(task); }
});
lock (tasks) { tasks.Add(task); }
}
private void start() {
DateTime start = DateTime.Now;
Console.WriteLine("Starting clients...");
int[] iList = new[] {
0,1,1,2,
10, 20, 30, 40};
foreach (int delay in iList) {
makeClient(delay, 0); ;
}
makeClient(15, 40);
Console.WriteLine("Done making");
tasks.Add(displayThreads());
waitForTasks(tasks);
Console.WriteLine("All done.");
}
private static void waitForTasks(List<Task> tasks) {
Task[] waitFor;
lock (tasks) {
waitFor = tasks.ToArray();
}
Task.WaitAll(waitFor);
}
此外,我試圖更換Delay(20)
與await Task.Run(() => Thread.Sleep(20))
Thread.Count
現在從29變為43和退縮到24,但是在多個符它從未掛起。
使用或不使用ThreadPool.SetMinThreads(500, 500)
TaskExt.Delay
ThreadPool.SetMinThreads(500, 500)
,使用TaskExt.Delay
的TaskExt.Delay都不會掛起。 (也就是說,即使切換1行代碼有時也會停止掛起,只是在我重新啟動項目4次后隨機繼續,但是我已經連續嘗試了6次而現在沒有任何問題)。
到目前為止,我已經嘗試了有無ThreadPool.SetMinThreads
上述所有操作, ThreadPool.SetMinThreads
未有任何不同。
Update2: CODE!
沒有看到更多代碼,很難做出進一步的猜測,但是我想總結一下這些注釋,這可能會在將來對其他人有所幫助:
我們發現這里的ThreadPool
口吃不是問題,因為ThreadPool.SetMinThreads(500, 500)
並沒有幫助。
任務工作流中的任何地方都存在SynchronizationContext
嗎? 將Debug.Assert(SyncrhonizationContext.Current == null)
各處進行檢查。 在每次await
使用ConfigureAwait(false)
。
是否有任何.Wait
, .WaitOne
, .WaitAll
, WaitAny
, .Result
在你的代碼的任何地方使用? 任何lock () { ... }
構造? Monitor.Enter/Exit
或任何其他阻止同步原語?
關於此: 我已經用Task.Delay(20)
替換了Task.Delay(20)
Task.Yield(); Thread.Sleep(20)
Task.Yield(); Thread.Sleep(20)
作為一種解決方法,可以正常工作。 但是,是的,我繼續嘗試弄清楚這里發生了什么,因為Task.Delay(20)可以拍攝如此遠的距離的想法使其完全無法使用。
確實,這聽起來令人擔憂。 Task.Delay
幾乎沒有錯誤,但一切Task.Delay
可能。 為了進行試驗,請嘗試使用await Task.Run(() => Thread.Sleep(20))
替換await Task.Delay(20)
await Task.Run(() => Thread.Sleep(20))
,同時將ThreadPool.SetMinThreads(500, 500)
await Task.Run(() => Thread.Sleep(20))
ThreadPool.SetMinThreads(500, 500)
保留在原位。
我還有一個使用非托管CreateTimerQueueTimer
API的Delay
的實驗實現(不同於Task.Delay
使用System.Threading.Timer
,后者又使用托管TimerQueue
)。 可以在這里找到要點 。 請隨意將其作為TaskExt.Delay
而不是標准Task.Delay
進行嘗試。 計時器回調已發布到ThreadPool
,因此該實驗仍應使用ThreadPool.SetMinThreads(500, 500)
。 我懷疑這可能會有所不同,但是我很想知道。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.