Masanori Nakazato
Masanori Nakazato

Reputation: 33

Task.Delay delays too long

I've created a multi task program. This program has around 20 main tasks and each of them calls some sub tasks to operate file I/Os. I wanted each main task to repeat periodically every 500ms, so I enterd the code Task.Delay(500).

The problem is Task.Delay delays a lot more than 500ms sometimes. There is a case it delays more than 3 seconds. How can I fix it?

The original progam is so big that I created a sample program below. (1) If Task.Delay is on, over-delay happens. (2) If Thead.Sleep is on, over-delay doesn't happen.

ThreadPool.SetMinThreads() doesn't seem to resolve it.

Thanks.

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();
            }
        }

    }
}

Upvotes: 1

Views: 1561

Answers (2)

Masanori Nakazato
Masanori Nakazato

Reputation: 33

It seems that I could find the answer. I changed the previous sample program like below. The main difference is using StopWatch or DateTime to measure time durations. In StopWatch version, many delays happen. In DateTime version, no or at least very little delays happen(s).

I guess that the cause is the contention of Timer that is used by both StopWatch and Task.Delay. I concluded that I should not use StopWatch and Task.Delay together.

Thank you.

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;

    }
}

Upvotes: 0

Soonts
Soonts

Reputation: 21926

I’ve run your test, with .NET 4.6.1 and VS 2017. Here on Xeon E3-1230 v3 CPU it never printed “Too late”, the Elapsed value was within 498-527 ms.

The Thread.Sleep version performed very similarly, 500-528ms per sleep, however the total execution time was much longer because the runtime refused to create 100 OS threads, that’s way too many, so less than 100 DelaysAsync functions ran in parallel. The debugger showed me there were 27 worker threads in Thread.Sleep version and only 9 worker threads in Task.Delay version.

I think you have other apps on your PC creating too many threads and consuming too much CPU. Windows tries to load balance threads evenly so when the whole system is CPU bound, more native threads = more CPU time and therefore less jitter.

If that’s your case and you want to prioritize your app in the scheduler, instead of using Thread.Sleep and more threads, raise the priority of your process.

Upvotes: 1

Related Questions