Task.Delay 延迟时间过长

Task.Delay delays too long

我创建了一个多任务程序。这个程序有大约20个主任务,每个主任务都会调用一些子任务来操作文件I/Os。我希望每个主要任务每 500 毫秒定期重复一次,所以我输入了代码 Task.Delay(500).

问题是 Task.Delay 有时延迟超过 500 毫秒。有延迟超过 3 秒的情况。 我该如何解决?

原程序太大,我在下面创建了一个示例程序。 (1) 如果Task.Delay 为on,则超延时发生。 (2) 如果Thead.Sleep打开,则不会发生超延时。

ThreadPool.SetMinThreads() 似乎无法解决。

谢谢。

class Program
{

    const int DELAY_TIME = 500;
    const int TASKS = 100;
    const int WAITS = 100;
    const int WARNING_THRESHOLD = 100;

    static void Main(string[] args)
    {
        //ThreadPool.SetMinThreads(workerThreads: 200, completionPortThreads: 200);

        Console.WriteLine("*** Start...");
        Test();
        Console.WriteLine("*** Done!");
        Console.ReadKey();
    }

    private static void Test()
    {
        List<Task> tasks = new List<Task>();
        for (int taskId = 0; taskId < TASKS; taskId++)
        {
            tasks.Add(DelaysAsync(taskId));
        }
        Task.WaitAll(tasks.ToArray());
    }

    static async Task DelaysAsync(int taskId)
    {
        await Task.Yield();

        Stopwatch sw = new Stopwatch();

        for (int i = 0; i < WAITS; i++)
        {
            sw.Reset();
            sw.Start();
            await Task.Delay(DELAY_TIME).ConfigureAwait(false);   // (1)
            //Thread.Sleep(DELAY_TIME);   // (2)
            sw.Stop();

            Console.Write($"Task({taskId})_iter({i}) Elapsed={sw.ElapsedMilliseconds}");
            if (sw.ElapsedMilliseconds > DELAY_TIME + WARNING_THRESHOLD)
            {
                Console.WriteLine(" *********** Too late!! ************");
            }
            else
            {
                Console.WriteLine();
            }
        }

    }
}

我已经 运行 你的测试,使用 .NET 4.6.1 和 VS 2017。在 Xeon E3-1230 v3 CPU 它从未打印过“Too late”,Elapsed 值是在 498-527 毫秒内

Thread.Sleep 版本执行非常相似,每次睡眠 500-528 毫秒,但是总执行时间要长得多,因为 运行time 拒绝创建 100 个 OS 线程,那是太多了,因此并行的 DelaysAsync 函数 运行 不到 100 个。调试器显示 Thread.Sleep 版本有 27 个工作线程,Task.Delay 版本只有 9 个工作线程。

我认为您的 PC 上有其他应用程序创建过多线程并消耗过多 CPU。 Windows 尝试均匀负载平衡线程,因此当整个系统受到 CPU 约束时,更多本机线程 = 更多 CPU 时间,因此抖动更少。

如果这是你的情况,并且你想在调度程序中确定你的应用程序的优先级,而不是使用 Thread.Sleep 和更多线程,你的进程 raise the priority

看来我能找到答案了。我更改了以前的示例程序,如下所示。主要区别在于使用 StopWatch 或 DateTime 来测量持续时间。 在 StopWatch 版本中,会发生许多延迟。 在 DateTime 版本中,不会或至少很少发生延迟。

我猜原因是 StopWatch 和 Task.Delay 都使用了 Timer 的争用。我的结论是我不应该同时使用 StopWatch 和 Task.Delay。

谢谢。

class Program
{

    const int DELAY_TIME = 500;
    const int TASKS = 100;
    const int WAITS = 100;
    const int WARNING_THRESHOLD = 500;

    static void Main(string[] args)
    {
        using (Process p = Process.GetCurrentProcess())
        {
            p.PriorityClass = ProcessPriorityClass.RealTime;

            //ThreadPool.SetMinThreads(workerThreads: 200, completionPortThreads: 200);
            int workerThreads;
            int completionPortThreads;
            ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
            Console.WriteLine($"{workerThreads}, {completionPortThreads}");

            Console.WriteLine("*** Start...");
            Test();
            Console.WriteLine("*** Done!");
            Console.ReadKey();
        }
    }

    private static void Test()
    {
        int totalCount = 0;
        List<Task<int>> tasks = new List<Task<int>>();
        for (int taskId = 0; taskId < TASKS; taskId++)
        {
            //tasks.Add(DelaysWithStopWatchAsync(taskId)); // many delays
            tasks.Add(DelaysWithDateTimeAsync(taskId));  // no delays
        }
        Task.WaitAll(tasks.ToArray());
        foreach (var task in tasks)
        {
            totalCount += task.Result;
        }
        Console.WriteLine($"Total counts of deday = {totalCount}");
    }

    static async Task<int> DelaysWithStopWatchAsync(int taskId)
    {
        await Task.Yield();

        int count = 0;
        Stopwatch sw = new Stopwatch();

        for (int i = 0; i < WAITS; i++)
        {
            sw.Reset();
            sw.Start();
            await Task.Delay(DELAY_TIME).ConfigureAwait(false);   // (1)
            //Thread.Sleep(DELAY_TIME);   // (2)
            sw.Stop();

            Console.Write($"task({taskId})_iter({i}) elapsed={sw.ElapsedMilliseconds}");
            if (sw.ElapsedMilliseconds > DELAY_TIME + WARNING_THRESHOLD)
            {
                Console.WriteLine($" *********** Too late!! ************");
                count++;
            }
            else
            {
                Console.WriteLine();
            }
        }
        return count;

    }

    static async Task<int> DelaysWithDateTimeAsync(int taskId)
    {
        await Task.Yield();

        int count = 0;

        for (int i = 0; i < WAITS; i++)
        {
            DateTime start = DateTime.Now;
            await Task.Delay(DELAY_TIME).ConfigureAwait(false);   // (1)
            //Thread.Sleep(DELAY_TIME);   // (2)
            DateTime end = DateTime.Now;
            int duration = (end - start).Milliseconds;

            Console.Write($"Task({taskId})_iter({i}) Elapsed={duration}");
            if (duration > DELAY_TIME + WARNING_THRESHOLD)
            {
                Console.WriteLine($" *********** Too late!! ************");
                count++;
            }
            else
            {
                Console.WriteLine();
            }
        }
        return count;

    }
}