Asynchronous Programming with async/await in C# (with simple example)

Jun 11, 2023ยท

3 min read

Asynchronous programming with async and await keywords in C# allows you to write code that can execute concurrently(parallelly) without blocking the main executing thread.

It enables you to handle time-consuming operations efficiently, such as network requests, database queries, or file I/O, without freezing the user interface or causing unnecessary delays.

Why asynchronous programming:
Synchronous programming executes the code line by line and the current task must be completed before moving to the next task. In general, it is slow and may not be suitable for time-consuming operations.

Key parts of async/await:

  1. async Modifier: When you mark a method with the async modifier, you indicate that it contains asynchronous operations. This allows you to use the await keyword inside the method.

  2. await Keyword: The await keyword is used to indicate that a particular operation should be awaited, allowing the calling code to proceed without blocking. It can only be used within an async method.

  3. Asynchronous Method Signature: An asynchronous method typically returns a Task or Task<T>, representing an asynchronous operation that may produce a result. The T denotes the type of the result if applicable.

Example of a simple asynchronous method:

public async Task PrepareTeaAsync()
{
    Console.WriteLine("Preparing Tea");
    await Task.Delay(3000);
}

In this example, the PrepareTeaAsync method is marked as async, indicating that it contains asynchronous operation. It uses await keyword to wait for the task completion. Task.Delay is used here to simulate an asynchronous task.

Here's an example to illustrate the difference between synchronous and asynchronous programming.

 internal class Program
    {
        static void Main(string[] args)
        {
            var stopwatch = new Stopwatch();
            Console.WriteLine("Bakery inc. Serving tea with snacks. Select serving speed - 1. Normal 2. Speed");
            switch (Console.ReadLine())
            {
                case "1":
                    stopwatch.Start();
                    PrepareTea();
                    PrepareSnacks();
                    break;
                case "2":
                    stopwatch.Start();
                    var teaTask = PrepareTeaAsync();
                    var snacksTask = PrepareSnacksAsync();
                    Task.WaitAll(teaTask, snacksTask);
                    break;
            }
            stopwatch.Stop();
            Console.WriteLine($"Serving Tea with snacks (Waiting time - {stopwatch.Elapsed})");
        }

        private static void PrepareTea()
        {
            Console.WriteLine("Preparing Tea");
            Task.Delay(3000).Wait();
        }
        private static void PrepareSnacks()
        {
            Console.WriteLine("Arranging snacks");
            Task.Delay(3000).Wait();
        }

        private static async Task PrepareTeaAsync()
        {
            Console.WriteLine("Preparing Tea");
            await Task.Delay(3000);
        }
        private static async Task PrepareSnacksAsync()
        {
            Console.WriteLine("Arranging snacks");
            await Task.Delay(3000);
        }
    }

In the above program, we used two options 1. normal (synchronous) 2. quick (asynchronous). Each option calls two methods with the same operations but later one additionally using async/await.

  1. Normal: This synchronous option is called two methods PrepareTea() and PrepareSnacks(). The first method takes 3 seconds to complete and the second method starts executing once the first method is completed. The latter method was completed after 3 seconds.

    The above output shows, the total time taken to complete the synchronous operation is 6 seconds.

  2. Speed: This asynchronous option is called two methods PrepareTeaAsync() and PrepareSnacksAsync(). Both methods started at the same time and each takes 3 seconds. Task.WaitAll waits for the two methods to complete.

    The above output shows, the total time taken to complete the asynchronous operation is 3 seconds. Since both methods started at the same time and ran in parallel, the total time is gradually reduced (almost half of synchronous!)

Asynchronous programming with async and await simplifies working with asynchronous operations, improves code readability, and enhances the responsiveness of applications by avoiding blocking calls. It is a powerful feature in C# for efficiently handling I/O-bound operations and making efficient use of system resources.