C# Async and Await: A Deep Dive with Code Comparisons

Estimated read time 4 min read

C# Async and Await: A Deep Dive with Code Comparisons

Asynchronous programming has become a fundamental aspect of modern software development, especially in scenarios where responsiveness and scalability are crucial. In C#, the async and await keywords provide a powerful way to work with asynchronous code, simplifying the development of responsive and efficient applications. In this article, we’ll explore the concepts of async and await through practical code comparisons, highlighting the benefits of asynchronous programming.

Understanding Asynchronous Programming in C#

Synchronous Code:

public void PerformSynchronousOperation()
{
    Console.WriteLine("Start synchronous operation");
    // Synchronous operation blocking the thread
    Thread.Sleep(2000);
    Console.WriteLine("End synchronous operation");
}

Asynchronous Code:

public async Task PerformAsynchronousOperation()
{
    Console.WriteLine("Start asynchronous operation");
    // Asynchronous operation without blocking the thread
    await Task.Delay(2000);
    Console.WriteLine("End asynchronous operation");
}

In the asynchronous example, the await keyword allows the thread to be released during the delay, enabling other tasks to execute in the meantime.

Comparing Performance:

Consider a scenario where multiple operations need to be performed concurrently. We’ll compare the performance of synchronous and asynchronous code.

Synchronous Approach:

public void PerformMultipleSynchronousOperations()
{
    Console.WriteLine("Start multiple synchronous operations");

    for (int i = 0; i < 5; i++)
    {
        PerformSynchronousOperation();
    }

    Console.WriteLine("End multiple synchronous operations");
}

Asynchronous Approach:

public async Task PerformMultipleAsynchronousOperations()
{
    Console.WriteLine("Start multiple asynchronous operations");

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

    for (int i = 0; i < 5; i++)
    {
        tasks.Add(PerformAsynchronousOperation());
    }

    await Task.WhenAll(tasks);

    Console.WriteLine("End multiple asynchronous operations");
}

In the asynchronous example, multiple operations can overlap, leading to potentially faster overall execution.

Error Handling:

Asynchronous code simplifies error handling through the use of try-catch blocks.

public async Task PerformAsyncOperationWithErrorHandling()
{
    try
    {
        // Asynchronous operation with potential exceptions
        await SomeAsyncMethod();
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
    }
}

Embracing Asynchronous Programming in C#

As demonstrated through these code comparisons, the async and await keywords in C# provide an elegant and efficient way to handle asynchronous operations. Whether dealing with I/O-bound tasks, parallel execution, or responsiveness in UI applications, asynchronous programming offers significant advantages. By releasing the thread during delays or asynchronous operations, developers can build applications that are more scalable and responsive, providing a smoother user experience.

In conclusion, mastering asynchronous programming in C# is a key skill for developers working on modern applications. Understanding the principles of async and await empowers developers to create high-performance and responsive software, adapting to the demands of contemporary software development.

Implement Database Async and Await

Certainly! Let’s extend the example to include asynchronous database operations using Entity Framework Core, a popular Object-Relational Mapping (ORM) framework in C#.

Adding Entity Framework Core:

Firstly, install the Entity Framework Core NuGet package for your project:

dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer

Creating a Database Context:

Define a simple database context class that inherits from DbContext:

// File: ApplicationDbContext.cs

using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : DbContext
{
    public DbSet<User> Users { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("your-connection-string");
    }
}

Model Class:

Create a simple model class for the database:

// File: User.cs

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

Asynchronous Database Operations:

Now, modify your FileUploadController to include asynchronous database operations:

// File: FileUploadController.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

[Route("api/[controller]")]
[ApiController]
public class FileUploadController : ControllerBase
{
    private readonly ApplicationDbContext _context;

    public FileUploadController(ApplicationDbContext context)
    {
        _context = context;
    }

    [HttpPost]
    public async Task<IActionResult> UploadFiles(List<IFormFile> files)
    {
        long size = files.Sum(f => f.Length);

        foreach (var formFile in files)
        {
            if (formFile.Length > 0)
            {
                var filePath = Path.Combine("uploads", formFile.FileName);

                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    await formFile.CopyToAsync(stream);
                }

                // Save information to the database asynchronously
                await SaveFileInfoToDatabase(formFile.FileName, filePath);
            }
        }

        return Ok(new { count = files.Count, size });
    }

    private async Task SaveFileInfoToDatabase(string fileName, string filePath)
    {
        var user = new User
        {
            Name = fileName,
            Email = filePath
        };

        // Asynchronous database operation
        _context.Users.Add(user);
        await _context.SaveChangesAsync();
    }
}

Summary:

In this example, we’ve integrated Entity Framework Core to perform asynchronous database operations when saving file information to the database. The use of async and await ensures that the application remains responsive during potentially time-consuming database interactions, enhancing the overall performance and user experience.

Related Articles