Asynchronous Programming with Async and Await in ASP.NET Core

Posted on April 14, 2023
Async/awaitMultithreadingAsynchronous vs synchronousAsynchronous ProgrammingParallel programming

Asynchronous Programming and Advantages

Asynchronous Programming is a way of programming that allows code programs to execute code as a Task without blocking a main thread, This means that the code can run continuously while waiting for other long-running operations to complete.

Async function and callbacks, allow the program to hand over the execution of a task to another thread or process, while the main thread can itself continue to run without blocking.

There are several advantages to using asynchronous programming

  1. Asynchronous Programming can improve the overall performance of your application by allowing multiple operations at the same time.
  2. A more concurrent request can be handled, this means a user can scale the application without requiring additional resources.
  3. A improvement in user experience, the program can respond more quickly to user input.
  4. Asynchronous programming can help reduce resource usage by allowing a program to free up system resources when they are not needed.

Difference between Synchronous vs Asynchronous

Synchronous and asynchronous are two different approaches to processing tasks, and they can have different effects on how quickly a user's registration process is completed and subsequent tasks like sending an email or adding them to a marketing or customer care group are carried out.

Asynchronous vs Synchronous

Synchronous processing means that each step in the process waits for the previous step to complete before moving on to the next one. In the context of user registration, this would mean that after a user registers, the system would wait for the email to be sent before adding the user to the marketing and customer care groups. This approach can ensure that each step is completed successfully before moving on to the next one, but it can also slow down the overall process.

On the other hand, asynchronous processing allows each step to be carried out independently of the others. In the context of user registration, this would mean that after a user registers, the system would immediately add them to the marketing and customer care groups and then send the email in the background. This approach can speed up the overall process, but it also carries the risk that one or more steps may fail, and there may not be a clear indication of which step failed or why.

How to relate to Multithreading in ASP.NET Core

ASP.NET Core provides several features to support multithreading, including the use of the async/await pattern to execute asynchronous operations, the use of Task Parallel Library (TPL) to execute parallel tasks, and the use of synchronization primitives such as locks and semaphores to ensure thread safety. We will discuss some of features here in this article.

The source code for this article can be found on GitHub.

Let's understand the difference with sample code.

namespace RegistrationConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Welcome to the Registration Console App!");
            Console.Write("Please enter your name: ");
            string name = Console.ReadLine();

            Console.Write("Please enter your email address: ");
            string email = Console.ReadLine();

            RegisterUser(name, email);

            Console.WriteLine("Thanks for registering!");
            Console.ReadKey();
        }

        static async void RegisterUser(string name, string email)
        {
            // Synchronous approach
            SendEmail(email);
            AddToMarketingGroup(email);
            AddToCustomerCareGroup(name, email);

            // Asynchronous approach
            await Task.Run(() => SendEmail(email));
            await Task.Run(() => AddToMarketingGroup(email));
            await Task.Run(() => AddToCustomerCareGroup(name, email));
        }

        static void SendEmail(string email)
        {
            Console.WriteLine($"Sending email to {email}...");
            Task.Delay(3000).Wait(); // Simulate a delay of 3 seconds
            Console.WriteLine($"Email sent to {email}!");
        }

        static void AddToMarketingGroup(string email)
        {
            Console.WriteLine($"Adding {email} to the marketing group...");
            Task.Delay(2000).Wait(); // Simulate a delay of 2 seconds
            Console.WriteLine($"{email} added to the marketing group!");
        }

        static void AddToCustomerCareGroup(string name, string email)
        {
            Console.WriteLine($"Adding {name} ({email}) to the customer care group...");
            Task.Delay(1000).Wait(); // Simulate a delay of 1 second
            Console.WriteLine($"{name} ({email}) added to the customer care group!");
        }
    }
}

In a synchronous approach, each task is executed one at a time in a sequential manner, and the next task cannot start until the previous task is complete. In the console application example, the synchronous approach is used to execute each task in order, without any delays or interruptions.

For example, when a user registers, the RegisterUser method is called, and it then calls the SendEmail method, followed by the AddToMarketingGroup method, and finally the AddToCustomerCareGroup method. Each method is executed synchronously, one after the other, and the next method cannot start until the previous one is complete.

In this example, the synchronous approach means that the console application will wait for each task to be complete before moving on to the next one. This can result in slower overall processing time, especially if one or more tasks take a long time to complete. However, synchronous processing can be simpler to implement and may be sufficient for applications with relatively small workloads.

In an asynchronous approach, each task is executed concurrently, without waiting for the previous task to complete. This allows multiple tasks to be executed simultaneously, which can result in faster overall processing time, especially if some of the tasks take a long time to complete.

In the console application example, the asynchronous approach is used to execute each task in a non-blocking manner. Instead of waiting for one task to complete before starting the next one, the application uses the await keyword to allow each task to run concurrently.

For example, when a user registers, the RegisterUser method is called, and it then uses the Task.Run method to call the SendEmail method, followed by the AddToMarketingGroup method, and finally the AddToCustomerCareGroup method. Each method is executed asynchronously, and the await keyword is used to wait for each task to complete before moving on to the next one.

Async/await pattern in ASP.NET Core

[HttpPost]
public async Task<IActionResult> RegisterUser(User user)
{
    try
    {
        // Create user
        var createdUser = await _userRepository.CreateUserAsync(user);

        // Send email asynchronously
        await _emailService.SendEmailAsync(createdUser.Email, "Welcome to our site", "Thank you for registering!");

        // Add to marketing group asynchronously
        await _groupService.AddUserToGroupAsync(createdUser, "marketing");

        // Add to customer care group asynchronously
        await _groupService.AddUserToGroupAsync(createdUser, "customer_care");

        return Ok(createdUser);
    }
    catch (Exception ex)
    {
        // Log the error
        _logger.LogError(ex, "Error registering user");

        // Return an error response
        return StatusCode(500, "An error occurred while registering the user.");
    }
}

In this example, each task that involves I/O or waiting for an external operation to complete is replaced with an asynchronous method call. The _emailService.SendEmailAsync and _groupService.AddUserToGroupAsync methods are marked as asynchronous and return a Task or Task<T>. The await keyword is used to asynchronously wait for each task to complete before continuing.

This allows the ASP.NET Core application to execute the tasks concurrently without blocking the thread, improving performance and responsiveness. By using the async/await pattern, the application can make optimal use of the available resources and handle more requests simultaneously.

Parallel programming in ASP.NET Core

Task.WhenAll to execute the asynchronous operations in parallel

static async Task RegisterUserAsync(string name, string email)
{
    var sendEmailTask = Task.Run(() => SendEmail(email));
    var addToMarketingGroupTask = Task.Run(() => AddToMarketingGroup(email));
    var addToCustomerCareGroupTask = Task.Run(() => AddToCustomerCareGroup(name, email));

    // Wait for all tasks to complete
    await Task.WhenAll(sendEmailTask, addToMarketingGroupTask, addToCustomerCareGroupTask);
}

In this example, we create three Task objects, one for each asynchronous operation. We then use the Task.WhenAll method to wait for all of the tasks to complete before returning control to the caller.

Summary

  1. Multithreading is a technique that allows multiple threads to execute concurrently within a single process, potentially improving the performance and responsiveness of the application.
  2. In ASP.NET Core, multithreading can be used to handle multiple requests simultaneously and to perform long-running operations in the background without blocking the main thread.
  3. Parallel programming is a technique that allows you to perform multiple tasks simultaneously, potentially improving the performance of your application. It can be especially useful for computationally intensive operations or operations that involve large data sets.
  4. In C#, you can use parallel programming constructs such as the Parallel class, Task.Run, and Task.WhenAll to write code that executes in parallel. However, it's important to use these constructs judiciously and consider factors such as thread safety, resource contention, and performance overhead. When used correctly, multithreading and parallel programming can help you create high-performance, responsive applications that provide a better user experience.

Thanks for reading!


Posted on April 14, 2023
Profile Picture

Arun Yadav

Software Architect | Full Stack Web Developer | Cloud/Containers

Subscribe
to our Newsletter

Signup for our weekly newsletter to get the latest news, articles and update in your inbox.