How to Execute Multiple Tasks in C#

How to Execute Multiple Tasks in C#

In this article, I am going to discuss How to Execute Multiple Tasks using the WhenAll Method in C# with Examples. Please read our previous article where we discussed How to Return a Value from a Task in C# with Examples.

How to Execute Multiple Tasks in C#?

So far, we have been executing one task at a time, but sometimes we will have many tasks that we want to execute simultaneously. We can do that with Task.WhenAll method. With Task.WhenAll we can have a list of tasks and all the tasks will be executed concurrently. And when all of the tasks are finished, we will be able to continue the execution of a method.

Example to Understand Task.WhenAll Method:

Let us understand how to execute multiple tasks concurrently using the Task.WhenAll method in C#. We are going to do an example in which we want to process multiple credit cards. For processing multiple credit cards.

We are going to use the following CreditCard class in our example. The following CreditCard class has two properties i.e. CardNumber and Name and also has one static method i.e. GenerateCreditCards to generate a collection of CreditCard. The GenerateCreditCards method takes one integer number as a parameter and then creates a collection of that many numbers of credit cards and returns that collection.

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

Next, we need to create one async method to process the credit cards. For this, we are creating the following ProcessCard Async method. This method takes the CreditCard as an input parameter and processes that credit card. Here, you can make any API call to process the Credit Card. But for simplicity, we are just delaying the execution for 1 second by using the Task,Delay asynchronous method and then print the message that the credit is processed and return a string containing the processed credit card information for future use if required.

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

Next, we are creating another asynchronous method where we will execute multiple tasks concurrently. For this purpose, we are creating the following ProcessCreditCards async method. This method takes the collection of cards that we want to be processed. Then by using the ForEach loop it processes the cards one by one by calling the ProcessCard async method. While calling the ProcessCard async method we are not using the await operator. The return type of ProcessCard is Task<string>. So here, I have created a collection of type Task<string> i.e. List< Task<string>> tasks, to store the response coming from the ProcessCard method. Next, we call the Task.WhenAll method by passing that Task<string> collection. And to check the time, here we are using a stopwatch and showing the time taken by the WhenAll method to process all the credit cards.

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

    foreach (var creditCard in creditCards)
    {
        var response = ProcessCard(creditCard);
        tasks.Add(response);
    }

    await Task.WhenAll(tasks);
    stopwatch.Stop();
    Console.WriteLine($"Processing of {creditCards.Count} Credit Cards Done in {stopwatch.ElapsedMilliseconds / 1000.0} Seconds");
}
Please note the following statement:
  1. await Task.WhenAll(tasks): This statement tells that there is a list of tasks. Please wait for all of the tasks to be done before continuing with the execution of this method and all of the tasks are going to be run simultaneously. As tasks contain 10 entries, so, all these 10 tasks are to be executed simultaneously.

Next, Modify the Main method as follows. From the main method, we are calling the static GenerateCreditCards method CreditCard class by passing an integer number i.e. 10 as an argument. This GenerateCreditCards method will return a collection of 10 Credit Cards. And then we are calling the ProcessCreditCards by passing that credit card collection as an argument.

static void Main(string[] args)
{
    Console.WriteLine($"Main Thread Started");

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

    ProcessCreditCards(creditCards);

    Console.WriteLine($"Main Thread Completed");
    Console.ReadKey();
}
Complete Example code:

Whatever we discussed as of now, everything is put in the below example.

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

namespace AsynchronousProgramming
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Main Thread Started");

            List<CreditCard> creditCards = CreditCard.GenerateCreditCards(10);
            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>>();

            //Processing the creditCards using foreach loop
            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");
            //foreach(var item in tasks)
            //{
            //    Console.WriteLine(item.Result);
            //}
        }
        
        public static async Task<string> ProcessCard(CreditCard creditCard)
        {
            //Here we can do any API Call to Process the Credit Card
            //But for simplicity we are just delaying the execution for 1 second
            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 Execute Multiple Tasks using the WhenAll Method in C# with Examples

You can see it is taking a little more than 1 second to process all the Credit Cards. One more point when we are executing multiple tasks concurrently, then you can never predict the order of execution. Now, let us observe the output. If you remember within the ProcessCard method we delayed the execution for one second. But after then as we execute multiple tasks using Task.WhenAll method, all the tasks execution is completed within little more than 1 second. This is because of Task.WhenAll method executes all the tasks concurrently which drastically improves the performance of our application.

Execution without Task.WhenAll Method in C#:

Now, let us execute the same application without using Task.WhenAll and observe how much time it is taking to process 10 credit cards. Please modify the ProcessCreditCards method as follows. Here, we remove the Task.WhenAll method and its related code. And here we are using await operator.

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

    foreach (var creditCard in creditCards)
    {
        var response = await ProcessCard(creditCard);
    }

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

With the above changes in place, now run the application and observe the output as shown in the below image.

Execution without Task.WhenAll Method in C#

You can see it is taking more than 10 seconds to process 10 credit cards compared to a little more than 1 second when using Task.WhenAll Method in C#. Now, I hope you understand when and how to use Task.WhenAll in C#.

Offloading the Current Thread – Task.Run Method in C#

Now. let us understand what do you mean by Offloading the Current Thread in C# with an example. Let us modify the example as follows. Now, we are trying to process 100000 credit cards. In the below example, we have removed the statement which prints the credit card details on the console. Further, we have used a stopwatch to check how much time the main thread takes.

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

Offloading the Current Thread - Task.Run Method in C#

You can see the Main thread taking approximately 9 seconds. Let us observe why? Please have a look at the below image. The following foreach loop of our ProcessCreditCards method runs 100000 times which will actually take some time, approximately 9 seconds. So, until the await Task.WhenAll(tasks) statement is called, our Main thread is frozen. As soon as we called await Task.WhenAll(tasks) method, the thread is active and starts processing.

How to Execute Multiple Tasks using the WhenAll Method in C# with Examples

We don’t want our Main thread to freeze for 9 seconds, because one of the main reasons to use asynchronous programming in C# is to have a responsive UI. So, we don’t want the UI or Main thread to be frozen.

How to overcome the above problem?

In any way, we need to make the Main Thread available. For that, we can offload the foreach loop to another thread by using the Task.Run Asynchronous Method in C#. Let us see how? Please have a look at the below image. We need to use Task.Run method and using a delegate we need to use the foreach loop. Further as Task.Run method is an asynchronous method, so we need to use the await operator as shown in the below image.

How to Execute Multiple Tasks using the WhenAll Method in C# with Examples

With the above changes, the foreach loop now going to be executed by another thread, and as we use the await method before Task.Run so the main thread will be free and continue its execution. The complete example code is given below.

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

With the above changes in place, now run the application and observe the output as shown in the below image. Now, the main thread is not frozen and is completed in milliseconds.

Task.Run method in C# with Examples

WhenAll Methods of Task Class in C#:

If you go to the definition of Task class, you will see that there are four overloaded versions of this method available. They are as follows:

  1. WhenAll(IEnumerable<Task> tasks): It Creates a task that will complete when all of the Task objects in an enumerable collection have been completed. Here, the parameter tasks specify the tasks to wait on for completion. It returns a task that represents the completion of all of the supplied tasks.
  2. WhenAll<TResult>(params Task<TResult>[] tasks): It creates a task that will complete when all of the Task objects in an array have been completed. Here, the parameter tasks specify the tasks to wait on for completion. The Type parameter TResult specifies the type of the completed task. It returns a task that represents the completion of all of the supplied tasks.
  3. WhenAll<TResult>(IEnumerable<Task<TResult>> tasks): It creates a task that will complete when all of the Task objects in an enumerable collection have been completed. Here, the parameter tasks specify the tasks to wait on for completion. The Type parameter TResult specifies the type of the completed task. It returns a task that represents the completion of all of the supplied tasks.
  4. WhenAll(params Task[] tasks): It creates a task that will complete when all of the Task objects in an array have been completed. Here, the parameter tasks specify the tasks to wait on for completion. It returns a task that represents the completion of all of the supplied tasks.

In the next article, I am going to discuss Limiting the Number of Concurrent Tasks in C# with Examples. Here, in this article, I try to explain How to Execute Multiple Tasks using the WhenAll Method in C# with Examples. I hope you enjoy this How to Execute Multiple Tasks using Task.WhenAll Method in C# article.

1 thought on “How to Execute Multiple Tasks in C#”

  1. blank

    Guys,
    Please give your valuable feedback. And also, give your suggestions about this How to Execute Multiple 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 Execute Multiple Tasks in C#, you can also share the same.

Leave a Reply

Your email address will not be published.