How to Limit Number of Concurrent Tasks in C#

How to Limit the Number of Concurrent Tasks in C#

In this article, I am going to discuss How to Limit the Number of Concurrent Tasks in C# using SemaphoreSlim with Examples. Please read our previous article where we discussed How to Execute Multiple Tasks using the WhenAll Method in C# with Examples. At the end of this article, you will understand the following two pointers in depth.

  1. How to Limit the Number of Concurrent Tasks in C#?
  2. How to Handle the Response of Multiple Tasks when executed using Task.WhenAll Method?
How to Limit the Number of Concurrent Tasks in C#?

In the below example, we are processing 100000 tasks concurrently.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;

namespace AsynchronousProgramming
{
    class Program
    {
        static void Main(string[] args)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            Console.WriteLine($"Main Thread Started");

            List<CreditCard> creditCards = CreditCard.GenerateCreditCards(100000);
            Console.WriteLine($"Credit Card Generated : {creditCards.Count}");
           
            ProcessCreditCards(creditCards);
            
            Console.WriteLine($"Main Thread Completed");
            stopwatch.Start();
            Console.WriteLine($"Main Thread Execution Time {stopwatch.ElapsedMilliseconds / 1000.0} Seconds");
            Console.ReadKey();
        }

        public static async void ProcessCreditCards(List<CreditCard> creditCards)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();

            var tasks = new List<Task<string>>();

            await Task.Run(() =>
            {
                foreach (var creditCard in creditCards)
                {
                    var response = ProcessCard(creditCard);
                    tasks.Add(response);
                }
            });
            
            //It will execute all the tasks concurrently
            await Task.WhenAll(tasks);
            stopwatch.Stop();
            Console.WriteLine($"Processing of {creditCards.Count} Credit Cards Done in {stopwatch.ElapsedMilliseconds/1000.0} Seconds");
        }
        
        public static async Task<string> ProcessCard(CreditCard creditCard)
        {
            await Task.Delay(1000);
            string message = $"Credit Card Number: {creditCard.CardNumber} Name: {creditCard.Name} Processed";
            return message;
        }
    }

    public class CreditCard
    {
        public string CardNumber { get; set; }
        public string Name { get; set; }

        public static List<CreditCard> GenerateCreditCards(int number)
        {
            List<CreditCard> creditCards = new List<CreditCard>();
            for (int i = 0; i < number; i++)
            {
                CreditCard card = new CreditCard()
                {
                    CardNumber = "10000000" + i,
                    Name = "CreditCard-" + i
                };

                creditCards.Add(card);
            }

            return creditCards;
        }
    }
}
Output:

How to Limit the Number of Concurrent Tasks in C#?

Here, we have processed 100000 tasks simultaneously. But there might be problems when we execute a huge number of tasks simultaneously, for example, the server might be unable to handle such a huge request, or if we send 100000 HTTP requests to a server, it might be blocked or down.

So, instead of sending 100000 HTTP requests at a time or processing 100000 tasks simultaneously, what we need to do is, we need to send them as a batch or process the tasks as a batch and we can do this in C# by using SimaphoreSlim. With SemaphoreSlim, we are able to limit the number of concurrent tasks that will be executed with the Task.WhenAll method. Let us understand this with an example.

Example to Understand How to Limit the Number of Concurrent Tasks in C# using SemaphoreSlim:

For a better understanding, we are not going to process 100000 Credit Cards. What we will do is we will process 15 credit cards with a batch of 3. That means five batches will be executed to process the 15 credit cards. Let us see how we can achieve this.

First, we need to create an instance of SemaphoreSlim class as follows. Here, we are passing the initial capacity as 3. That means at a time 3 threads are allowed to execute the tasks.

SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3);

So, here, what SemaphoreSlim is doing is if we have more than 3 tasks been running, we are going to wait and we’re going to wait until the semaphore gets released. If you are new to SimaphoreSlim, please read the following article where we discussed SimaphoreSlim in detail.

https://dotnettutorials.net/lesson/semaphoreslim-class-in-csharp/

Next, we need to convert the following piece of code of our ProcessCreditCards method to use SemaphoreSlim.

Example to Understand How to Limit the Number of Concurrent Tasks in C# using SemaphoreSlim

The following code shows how to use SimaphoreSlim to limit the number of concurrent tasks to be executed simultaneously. As we are using the WaitAsync method so, we need to use the async lambda expression, and also, we need to use await operator while calling the ProcessCard function. We need to release the semaphore inside the finally block which make sure that if an exception occurred then also the semapohoreslim object release the thread so that other task can be executed by the thread.

How to Limit the Number of Concurrent Tasks in C#

Complete Example Code:

The following is the complete example code that shows how to use SemaphoreSlim to limit the number of concurrent tasks. Here, it will execute the tasks in batches, and in each batch, it will execute a maximum of three tasks. In the below example, we need to include the System.Threading and System.Linq namespaces. SemaphoreSlim class belongs to System.Threading namespace and as we use LINQ queries so we need to include System.Linq namespace.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;

namespace AsynchronousProgramming
{
    class Program
    {
        //Allowing Maximum 3 tasks to be executed at a time
        static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3);
        static void Main(string[] args)
        {
            var stopwatch = new Stopwatch();  
            Console.WriteLine($"Main Thread Started");

            //Generating 15 Credit Cards
            List<CreditCard> creditCards = CreditCard.GenerateCreditCards(15);
            Console.WriteLine($"Credit Card Generated : {creditCards.Count}");
           
            ProcessCreditCards(creditCards);
            
            Console.WriteLine($"Main Thread Completed");
            Console.ReadKey();
        }

        public static async void ProcessCreditCards(List<CreditCard> creditCards)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();

            var tasks = new List<Task<string>>();

            //Need to use async lambda expression
            tasks = creditCards.Select(async card =>
            {
                //This will tell if we have more than 4000 tasks are running, 
                //we are going to wait and '
                //we're going to wait until the semaphore gets released.
                await semaphoreSlim.WaitAsync();

                //Need to use await operator here as we are using asynchronous WaitAsync
                try
                {
                    return await ProcessCard(card);
                }
                finally
                {
                    //Release the semaphore
                    semaphoreSlim.Release();
                }
                
            }).ToList();
            
            //It will execute a maximum of 3 tasks at a time
            await Task.WhenAll(tasks);
            stopwatch.Stop();
            Console.WriteLine($"Processing of {creditCards.Count} Credit Cards Done in {stopwatch.ElapsedMilliseconds/1000.0} Seconds");
        }
        
        public static async Task<string> ProcessCard(CreditCard creditCard)
        {
            await Task.Delay(1000);
            string message = $"Credit Card Number: {creditCard.CardNumber} Name: {creditCard.Name} Processed";
            Console.WriteLine($"Credit Card Number: {creditCard.CardNumber} Processed");
            return message;
        }
    }

    public class CreditCard
    {
        public string CardNumber { get; set; }
        public string Name { get; set; }

        public static List<CreditCard> GenerateCreditCards(int number)
        {
            List<CreditCard> creditCards = new List<CreditCard>();
            for (int i = 0; i < number; i++)
            {
                CreditCard card = new CreditCard()
                {
                    CardNumber = "10000000" + i,
                    Name = "CreditCard-" + i
                };

                creditCards.Add(card);
            }

            return creditCards;
        }
    }
}
Output:

How to Limit the Number of Concurrent Tasks in C# using SemaphoreSlim with Examples

Here, please observe the output. It is taking a little more than 5 seconds and this is expected. Because it will execute all the tasks in five batches. And if you notice we have delayed the ProcessCard Execution by 1 second. That means executing one batch will take little more than 1 second and the same for all 5 batches, and hence the overall time is little more than 5 seconds.

How to Handle Response when Executing Multiple Tasks using Tasks.WhenAll Method in C#?

Now, let us understand how to handle the response when executing multiple tasks concurrently using Tasks.WhenAll Method in C#. We know Tasks.WhenAll tells that please wait for all of the tasks to be done before continuing with the execution of the rest part of the method. That means it will make sure once all the tasks are completed then only, we can proceed further to execute the rest part of the method.

If you further notice the return type of the ProcessCard card method is Task<string>. That means the method is returning something. As the WhenAll method execute all the tasks that means all the tasks return some data. How we can fetch such that? Let us see. Please have a look at the below image. If you put the mouse pointer over the await operator, then you will see that it is returning a string array.

How to Handle Response when Executing Multiple Tasks using Tasks.WhenAll Method in C#?

So, we can store the response in a string array as follows:

string[] Responses= await Task.WhenAll(tasks);

Then by using a foreach loop we can access the result of each task as follows.

How to Handle Response when Executing Multiple Tasks using Tasks.WhenAll Method in C#?

Complete Example Code:

Whatever we discussed is shown in the below example.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;

namespace AsynchronousProgramming
{
    class Program
    {
        //Allowing Maximum 3 tasks to be executed at a time
        static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3);
        static void Main(string[] args)
        {
            var stopwatch = new Stopwatch();
            Console.WriteLine($"Main Thread Started");

            //Generating 15 Credit Cards
            List<CreditCard> creditCards = CreditCard.GenerateCreditCards(15);
            Console.WriteLine($"Credit Card Generated : {creditCards.Count}");

            ProcessCreditCards(creditCards);

            Console.WriteLine($"Main Thread Completed");
            Console.ReadKey();
        }

        public static async void ProcessCreditCards(List<CreditCard> creditCards)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();

            var tasks = new List<Task<string>>();

            //Need to use async lambda expression
            tasks = creditCards.Select(async card =>
            {
                await semaphoreSlim.WaitAsync();

                try
                {
                    return await ProcessCard(card);
                }
                finally
                {
                    semaphoreSlim.Release();
                }

            }).ToList();


            string[] Responses = await Task.WhenAll(tasks);
            //var Responses = await Task.WhenAll(tasks);

            foreach (var response in Responses)
            {
                Console.WriteLine(response);
            }

            stopwatch.Stop();
            Console.WriteLine($"Processing of {creditCards.Count} Credit Cards Done in {stopwatch.ElapsedMilliseconds / 1000.0} Seconds");
        }

        public static async Task<string> ProcessCard(CreditCard creditCard)
        {
            await Task.Delay(1000);
            string message = $"Credit Card Number: {creditCard.CardNumber} Name: {creditCard.Name} Processed";
            //Console.WriteLine($"Credit Card Number: {creditCard.CardNumber} Processed");
            return message;
        }
    }

    public class CreditCard
    {
        public string CardNumber { get; set; }
        public string Name { get; set; }

        public static List<CreditCard> GenerateCreditCards(int number)
        {
            List<CreditCard> creditCards = new List<CreditCard>();
            for (int i = 0; i < number; i++)
            {
                CreditCard card = new CreditCard()
                {
                    CardNumber = "10000000" + i,
                    Name = "CreditCard-" + i
                };

                creditCards.Add(card);
            }

            return creditCards;
        }
    }
}
Output:

Example to Understand How to Handle Response when Executing Multiple Tasks using Tasks.WhenAll Method in C#

Real-Time Example:

In the real-time application when we are calling an API, we will not get any string data. In most cases, we will get JSON data. If we are getting some JSON data then how we will handle it? We need to use JSON Serializer. To use JSON serializer, first, we need to include Newtonsoft.Json DLL from the NuGet. Once you install Newtonsoft.Json DLL from the NuGet, then modify the code as follows which shows how to use JSON serializer. The following example code is self-explained, so please go through the comment lines.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using Newtonsoft.Json;

namespace AsynchronousProgramming
{
    class Program
    {
        //Allowing Maximum 3 tasks to be executed at a time
        static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3);
        static void Main(string[] args)
        {
            var stopwatch = new Stopwatch();
            Console.WriteLine($"Main Thread Started");

            //Generating 15 Credit Cards
            List<CreditCard> creditCards = CreditCard.GenerateCreditCards(15);
            Console.WriteLine($"Credit Card Generated : {creditCards.Count}");

            ProcessCreditCards(creditCards);

            Console.WriteLine($"Main Thread Completed");
            Console.ReadKey();
        }

        public static async void ProcessCreditCards(List<CreditCard> creditCards)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();

            var tasks = new List<Task<string>>();

            //Need to use async lambda expression
            tasks = creditCards.Select(async card =>
            {
                await semaphoreSlim.WaitAsync();

                try
                {
                    return await ProcessCard(card);
                }
                finally
                {
                    semaphoreSlim.Release();
                }

            }).ToList();

            //Return the response a string array
            var Responses = await Task.WhenAll(tasks);

            //Creating a collection to hold the responses
            List<CreditCardResponse> creditCardResponses = new List<CreditCardResponse>();

            //Looping through the string array
            foreach (var response in Responses)
            {
                //Here, the string is a JSON string
                //Converting the JSON String to .NET Object (CreditCardResponse) using
                //JsonConvert class DeserializeObject
                CreditCardResponse creditCardResponse = JsonConvert.DeserializeObject<CreditCardResponse>(response);

                //Adding the .NET Object into the resposne collection
                creditCardResponses.Add(creditCardResponse);
            }

            //Printing all the approved credit cards using a foreach loop
            Console.WriteLine("\nApproved Credit Cards");
            foreach(var item in creditCardResponses.Where(card => card.IsProcessed == true))
            {
                Console.WriteLine($"Card Number: {item.CardNumber}, Name: {item.Name}");
            }

            //Printing all the rejected credit cards using a foreach loop
            Console.WriteLine("\nRejected Credit Cards");
            foreach (var item in creditCardResponses.Where(card => card.IsProcessed == false))
            {
                Console.WriteLine($"Card Number: {item.CardNumber}, Name: {item.Name}");
            }
        }

        public static async Task<string> ProcessCard(CreditCard creditCard)
        {
            await Task.Delay(1000);
            
            var creditCardResponse = new CreditCardResponse
            {
                CardNumber = creditCard.CardNumber,
                Name = creditCard.Name,

                //Logic to Decide whether the card is processed or rejected
                //If modulus 2 is 0, the processed else rejected
                IsProcessed = creditCard.CardNumber % 2 == 0 ? true : false
            };

            //Converting the .NET Object to JSON string
            string jsonString = JsonConvert.SerializeObject(creditCardResponse);

            //Return the JSON String
            return jsonString;
        }
    }

    public class CreditCard
    {
        public long CardNumber { get; set; }
        public string Name { get; set; }

        public static List<CreditCard> GenerateCreditCards(int number)
        {
            List<CreditCard> creditCards = new List<CreditCard>();
            for (int i = 0; i < number; i++)
            {
                CreditCard card = new CreditCard()
                {
                    CardNumber = 10000000 + i,
                    Name = "CreditCard-" + i
                };

                creditCards.Add(card);
            }

            return creditCards;
        }
    }

    //This class will hold the response after processing the Credit card
    public class CreditCardResponse
    {
        public long CardNumber { get; set; }
        public string Name { get; set; }
        public bool IsProcessed { get; set; }
    }
}
Output:

How to Limit the Number of Concurrent Tasks in C# using SemaphoreSlim with Examples

In the next article, I am going to discuss How to Cancel a Long-Running Task using Cancellation Token in C# with Examples. Here, in this article, I try to explain How to Limit the Number of Concurrent Tasks in C# using SemaphoreSlim with Examples. I hope you enjoy this How to Limit the Number of Concurrent Tasks in C# using SemaphoreSlim with Examples article.

4 thoughts on “How to Limit Number of Concurrent Tasks in C#”

  1. Guys,
    Please give your valuable feedback. And also, give your suggestions about this How to Limit the Number of Concurrent Tasks in C# concept. If you have any better examples, you can also put them in the comment section. If you have any key points related to How to Limit the Number of Concurrent Tasks in C#, you can also share the same.

  2. mohammed abdel hameed

    I appreciate too much your excellent explanation of the articles, but I am faceing a big problem As I am a beginner C# programmer and I could not follow the steps of implementing the programs, for example in an article.
    )How to Execute Multiple Tasks using the WhenAll Method in C#  (
    How to Limit Number of Concurrent Tasks in C# etc
    I suggest “without using the arrows” to write the line number that must be after the current line that make tracking me step by step, this is helping to save time and efforts.
    thanks

  3. Task.Run() creates a thread from ThreadPool which is a background thread. so, without await the main thread does not wait for the background thread. As Tasks are an abstraction for Threads, is there a way to create a foreground thread using Task?

Leave a Reply

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