Task.Delay延迟太长时间

时间:2018-03-30 01:53:42

标签: c# .net asynchronous async-await task-parallel-library

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

问题是Task.Delay有时会延迟超过500ms。有一种情况,它延迟超过3秒。 我该如何解决?

原始程序非常大,我在下面创建了一个示例程序。 (1)如果Task.Delay打开,则发生过度延迟。 (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();
            }
        }

    }
}

2 个答案:

答案 0 :(得分:1)

我使用.NET 4.6.1和VS 2017进行测试。在Xeon E3-1230 v3 CPU中,它从未打印过“太晚”,经过的值在498-527 ms之内。

Thread.Sleep版本执行非常相似,每次睡眠500-528ms,但总执行时间要长得多,因为运行时拒绝创建100个OS线程,这太多了,所以少于100个DelaysAsync函数并行运行。调试器显示我在Thread.Sleep版本中有27个工作线程,在Task.Delay版本中只有9个工作线程。

我认为您的PC上还有其他应用程序会创建太多线程并占用过多CPU。 Windows尝试均匀地平衡线程,因此当整个系统受CPU限制时,更多的本机线程=更多的CPU时间,因此抖动更少。

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

答案 1 :(得分:0)

似乎我能找到答案。我改变了以前的示例程序,如下所示。主要区别在于使用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;

    }
}