簡體   English   中英

等待Task.Delay(foo); 需要幾秒鍾而不是毫秒

[英]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.WaitAllWaitAny.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM