Limit number of tasks running at the same time in .NET

How to throttle number of running tasks to a specific number in C#

Limiting the number concurrently running Tasks is not that often requirement in a code, but eventually you'll find yourself in the situation that for the most likely performance issues you'll have to limit number of concurrently running Tasks.

One of these situations is when you have long running operations in the Task. If you spin up to many Tasks you will seriously degrade your application performance. This can be easily done with System.Threading.Semaphore class which is introduced in .NET Framework 2. It is a robust class which can also provide concurrency limitation for the multiple processes as well as inside the process. Since we are only going to deal with Task concurrency inside the single process we'll use System.Threading.SemaphoreSlim. It is a light-weight class for managing number of concurrently executing methods which is introduced in .NET Framework 4.

The following a simple example of System.Threading.SemaphoreSlim usage. The maximum number of executing tasks is set to 3 and we'll try to run 4 tasks at the same time.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TaskLimitSample
{
    class Program
    {
        static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3);

        static void Main(string[] args)
        {

            for (int t = 0; t < 4; t++)
            {
                semaphoreSlim.Wait();
                Task.Factory.StartNew((index) =>
                {
                    //Simulate processiong
                    Console.WriteLine($"Running task: {index}");
                    Thread.Sleep(TimeSpan.FromSeconds(5));

                },t).ContinueWith(task => { semaphoreSlim.Release(); });
            }

            Console.ReadLine();
        }
    }
}

    

To simulate long running processing I used simple Thread.Sleep method. When you run the code above you will see only first three tasks executed right away, but the last 4th task will be executed with a delay of 3 seconds which is the delay value of the Thread.Sleep method parameter.

Method SemaphoreSlim.Wait is a void method which will block the executing thread until all Tasks are finished. If one of your Tasks is "stuck" your invoking thread will be waiting for it to end. The nice thing is that Wait method has several overloads and one of them is a void method which accepts CancellationToken class instance.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TaskLimitSample
{
    class Program
    {
        static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3);

        static void Main(string[] args)
        {

            for (int t = 0; t < 4; t++)
            {
                using (var tokenSource = new CancellationTokenSource())
                {
                    tokenSource.CancelAfter(TimeSpan.FromSeconds(3));
                    try
                    {
                        semaphoreSlim.Wait(tokenSource.Token);

                        Task.Factory.StartNew((index) =>
                        {
                            //Simulate processiong
                            Console.WriteLine($"Running task: {index}");
                            Thread.Sleep(TimeSpan.FromSeconds(5));

                        }, t).ContinueWith(task => { semaphoreSlim.Release(); });
                    }
                    catch(OperationCanceledException ex)
                    {
                        Console.WriteLine("Wait time expired before semaphore released");
                    }
                }

            }

            Console.ReadLine();
        }
    }
}

    

Once CancellationToken is canceled, in our case when timeout expires, it will throw OpeartionCancelledException which means Semaphore did not get released before the CancellationToken expired.

Running task: 2
Running task: 1
Running task: 0
Wait time expired before semaphore released

Another overload of Wait method returns Boolean value and have expiration parameter which can be either System.TimeSpan or System.Threading.CancellaonToken. After the expiration condition is reached, code will continue running without waiting for semaphore to release.

The following example will wait for semaphore to be released for 3 seconds. Since out task code will finish in 5 seconds, we will reach the Console message code which will inform that wait time expired before at least one task finished.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TaskLimitSample
{
    class Program
    {
        static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3);

        static void Main(string[] args)
        {

            for (int t = 0; t < 4; t++)
            {
                if (semaphoreSlim.Wait(TimeSpan.FromSeconds(3)))
                {
                    Task.Factory.StartNew((index) =>
                    {
                    //Simulate processiong
                    Console.WriteLine($"Running task: {index}");
                        Thread.Sleep(TimeSpan.FromSeconds(5));

                    }, t).ContinueWith(task => { semaphoreSlim.Release(); });
                }
                else
                {
                    Console.WriteLine("Wait time expired before semaphore released");
                }
            }

            Console.ReadLine();
        }
    }
}

    

If you need to limit Task executing in at the same time, one of these usages of System.Threading.SemaphoreSlim should be working for you.

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article