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.