Tasks do scale (well enough)

Spread the love

I’ve said it before and I’ll say it again: threads don’t scale. But what about Tasks?

Here is a simple application which spawns a large (configurable) amount of periodic background tasks:

namespace TaskDelaySample
{
    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;

    internal sealed class Program
    {
        private static void Main(string[] args)
        {
            int totalCount = int.Parse(args[0]);

            Console.WriteLine("Press ENTER to start.");
            Console.ReadLine();

            List<PeriodicTask> tasks = new List<PeriodicTask>();

            StartWork(totalCount, () => Thread.Sleep(1), tasks);

            Console.WriteLine("Press ENTER to stop.");
            Console.ReadLine();

            StopWork(tasks);

            Console.WriteLine("Done.");
        }

        private static void StopWork(List<PeriodicTask> tasks)
        {
            List<Task> pending = new List<Task>();
            foreach (PeriodicTask task in tasks)
            {
                pending.Add(task.StopAsync());
            }

            Task.WaitAll(pending.ToArray());
        }

        private static void StartWork(int totalCount, Action workAction, List<PeriodicTask> tasks)
        {
            int batchCount = 100;
            int batchSize = totalCount / batchCount;
            for (int i = 0; i < batchCount; ++i)
            {
                Thread.Sleep(1);
                for (int j = 0; j < batchSize; ++j)
                {
                    int id = j + (i * batchCount);
                    PeriodicTask task = new PeriodicTask(workAction, TimeSpan.FromMilliseconds(100.0d));
                    tasks.Add(task);
                    task.Start();
                }
            }
        }

        private sealed class PeriodicTask
        {
            private readonly Action action;
            private readonly TimeSpan interval;

            private CancellationTokenSource cts;
            private Task runningTask;
            private volatile bool hasRun;

            public PeriodicTask(Action action, TimeSpan interval)
            {
                this.action = action;
                this.interval = interval;
            }

            public bool HasRun
            {
                get { return this.hasRun; }
            }

            public void Start()
            {
                this.cts = new CancellationTokenSource();
                this.runningTask = this.LoopAsync(this.cts.Token);
            }

            public async Task StopAsync()
            {
                using (this.cts)
                {
                    this.cts.Cancel();
                    await this.runningTask;
                    this.runningTask = null;
                }

                this.cts = null;
            }

            private async Task LoopAsync(CancellationToken token)
            {
                try
                {
                    while (!token.IsCancellationRequested)
                    {
                        await Task.Delay(this.interval, token);
                        this.action();
                        this.hasRun = true;
                    }
                }
                catch (OperationCanceledException)
                {
                }
            }
        }
    }
}

The tasks themselves are technically CPU bound, though they don’t actually do anything besides sleep for one millisecond (simulating “blocking” work). The scheduling interval of each task is (ostensibly) 100 milliseconds.

I’ve collected some performance counter graphs for 1000, 10000, and 100000 tasks:
1000 tasks
10000 tasks
100000 tasks

The results appear to show pretty good stability for 1000 tasks, but things get slower with 10000 and even more so with 100000 (the thread count was still growing for both of those runs when I stopped).

The verdict? As we could have guessed, tasks for periodic actions do scale — at least better than dedicated threads. Just don’t go crazy with them.

Leave a Reply

Your email address will not be published. Required fields are marked *