Asynchronous Programming in C#

Using synchronous in modern applications is a bad practice as code blocks the thread executing it from doing any other work as the application involves callbacks, events or waiting for the response from the APIs. This makes the application freeze and not usable until the work is completed and the thread is released.

The below code is an example of synchronous code.

static void GetDemographicData()
{
    var states = GetStates();
    Console.WriteLine("States");

    var districts = GetDistricts();
    Console.WriteLine("Districts");
}

Using Async and Await keywords

Async and Await keywords provide a way of nonblocking way of starting a task and continuing the execution after the task is completed. Let's update the above code with the Async and Await keywords.

static async Task GetDemographicData() 
{
    var states = await GetStates();
    Console.WriteLine("States");

    var districts = await GetDistricts();
    Console.WriteLine("Districts");
}

Though the above code is not completely asynchronous it serves the purpose of executing the code sequentially and not blocking the thread.

What does Async/Await do?

  • Async marks the method as asynchronous and it creates a new worker thread where the work will be completed which avoids the blocking of the UI/API/Apps main thread.

  • Await retrieves the result from an async call when the result is available. It also validates any exception while it is waiting for the result. Finally, it introduces continuation getting back from the worker thread to the main thread.

On a high level, this is how the continuation happens to resume the work in the main thread after the work is completed in the worker thread.

class MyTask
{
    private bool _completed; //to know whether task is completed or not
    private Exception? _error; // store if any exception that caused Task to fail
    private Action<MyTask>? _continuation; //delegate to be invoked after Task completes
    //If task is completed, then it queues the action in threadpool
    //else it captures the execution context and waits for the completion
    private ExecutionContext? _ec; //stores exec context for resuming the delegate
}

When to use Async/Await?

  • I/O bound operations - Database/Disk/Memory/Web APIs/Web Services.

  • CPU-bound operations - For intense operations, we can use TPL or spawn new threads to speed up the execution.

Important points to remember

  • A thread is defined as the execution path of a program.

  • Always use async and await together.

  • The task is a representation of an asynchronous operation.

  • Always return Task instead of void.

  • The exception thrown from an async method returning Task is captured and placed on the Task object which can be caught outside the method unlike the exception thrown from an async method returning void.

  • Using Task.Result or Task.Wait() will block the thread and may deadlock the application until the result is available as the code will run synchronously.

      var client = new HttpClient();
      var response =  client.GetAsync($"{url}").Result; // bad approach.Use await instead.
    

Did you find this article valuable?

Support Sankarshan Ramesh by becoming a sponsor. Any amount is appreciated!