Master C# Programming From Scratch

Clear, interactive, and structured coding lessons designed for absolute beginners.

Asynchronous Programming

Asynchronous programming allows a C# application to start a task without forcing the application to wait for that task to finish before continuing with other work.

Key Idea: Async programming is especially useful when an application is waiting for operations such as database calls, API responses, file operations, network requests, or other I/O work.

What is Asynchronous Programming?

Asynchronous programming is a programming approach where a long-running operation can be started and the application can continue doing other useful work while that operation is in progress.

In C#, asynchronous programming is mainly implemented using async and await keywords together with the Task type.

Simple Example: Imagine ordering food in a restaurant. You place the order and instead of standing at the kitchen waiting, you can sit and do something else. When the food is ready, you continue with it. This is similar to asynchronous programming.

Why Do We Need Asynchronous Programming?

Some operations take time to complete. For example:

  • Calling a Web API
  • Reading data from a database
  • Writing or reading a file
  • Downloading data from the internet
  • Sending an HTTP request
  • Waiting for an external service

If these operations are handled synchronously, the current flow may remain waiting until the operation finishes. With asynchronous programming, the application can avoid unnecessarily blocking while waiting for I/O operations.

Synchronous vs Asynchronous Programming

Feature Synchronous Asynchronous
Execution Waits for the operation Can continue while waiting
Waiting Current flow may remain blocked Waiting can be handled asynchronously
Best suited for Simple CPU work I/O-bound operations
Common examples Simple calculations API, database and file operations

Understanding Task

A Task represents an asynchronous operation. It can represent work that is currently running, waiting, or has already completed.

A method that performs asynchronous work commonly returns Task or Task<T>.

C#
Task DoWorkAsync()
{
    // asynchronous work
}

When the asynchronous operation does not return a value, Task is generally used.

C#
Task<int> GetNumberAsync()
{
    // returns an integer asynchronously
}

When an asynchronous operation returns a value, Task<T> is used.

async Keyword

The async keyword tells C# that a method contains asynchronous operations and may use the await keyword.

C#
async Task DoWorkAsync()
{
    // asynchronous code
}
Remember: async does not automatically create a new thread. It enables a method to work with asynchronous operations.

await Keyword

The await keyword is used to asynchronously wait for a Task to complete.

C#
await Task.Delay(2000);

In this example, the method waits asynchronously for two seconds. The important point is that this waiting does not require the method to synchronously block the thread for the entire delay.

Example 1: Basic async and await

C#
using System;
 
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Console.WriteLine("Program started");

        await Task.Delay(2000);

        Console.WriteLine("Async operation completed");

        Console.WriteLine("Program finished");
    }
}

Output

Output
Program started
Async operation completed
Program finished

Explanation

  1. The program prints Program started.
  2. Task.Delay(2000) represents an asynchronous delay.
  3. await waits for the Task to complete.
  4. After two seconds, the next statement executes.
  5. The program prints Program finished.

Example 2: Async Method Without Return Value

An asynchronous method that does not return a result normally uses Task.

C#
using System;
using System.Threading.Tasks;

class Program
{
    static async Task ShowMessageAsync()
    {
        await Task.Delay(1000);

        Console.WriteLine("Hello from asynchronous method");
    }

    static async Task Main()
    {
        Console.WriteLine("Before method call");

        await ShowMessageAsync();

        Console.WriteLine("After method call");
    }
}

Output

Output
Before method call
Hello from asynchronous method
After method call

Example 3: Async Method Returning a Value

When an asynchronous method returns a value, use Task<T>.

C#
using System;
 
using System.Threading.Tasks;

class Program
{
    static async Task<int> GetNumberAsync()
    {
        await Task.Delay(1000);

        return 100;
    }

    static async Task Main()
    {
        int number = await GetNumberAsync();

        Console.WriteLine("Number = " + number);
    }
}

Output

Output
Number = 100

Here, GetNumberAsync() returns Task<int>. The await keyword gets the final integer result from the completed Task.

Example 4: Calling a Web API Asynchronously

Calling an external API is one of the most common real-world uses of asynchronous programming in .NET applications.

C#
using System;
 
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        using HttpClient client = new HttpClient();

        string response =
            await client.GetStringAsync("https://example.com");

        Console.WriteLine(response);
    }
}
Important: In a real application, use the actual API endpoint required by your project. The example above demonstrates the asynchronous HTTP request pattern.

The application does not need to synchronously block while waiting for the network response. Once the response is available, execution continues after the await.

Asynchronous Programming with Database Operations

Database access can also be performed asynchronously. This is particularly useful in ASP.NET Core applications where many requests may be handled at the same time.

C#
public async Task<List<Student>> GetStudentsAsync()
{
    return await _context.Students.ToListAsync();
}

Here, the database operation is asynchronous. The application can avoid unnecessarily blocking while the database server processes the request.

Running Multiple Async Operations

Sometimes an application needs to perform multiple independent asynchronous operations. In such situations, the Tasks can be created first and then awaited together.

C#
using System;
 
using System.Threading.Tasks;

class Program
{
    static async Task<string> GetDataAsync(string name)
    {
        await Task.Delay(2000);

        return "Data received from " + name;
    }

    static async Task Main()
    {
        Task<string> task1 = GetDataAsync("Service 1");
        Task<string> task2 = GetDataAsync("Service 2");

        string result1 = await task1;
        string result2 = await task2;

        Console.WriteLine(result1);
        Console.WriteLine(result2);
    }
}

Output

Output
Data received from Service 1
                Data received from Service 2

Since both operations are started before awaiting their results, independent asynchronous operations can make progress without unnecessarily waiting for one to finish before starting the other.

Task.WhenAll()

Task.WhenAll() is useful when multiple independent asynchronous operations need to complete before continuing.

C#
using System;
 
using System.Threading.Tasks;

class Program
{
    static async Task<string> GetDataAsync(int id)
    {
        await Task.Delay(1000);

        return "Data " + id;
    }

    static async Task Main()
    {
        Task<string> task1 = GetDataAsync(1);
        Task<string> task2 = GetDataAsync(2);
        Task<string> task3 = GetDataAsync(3);

        string[] results =
            await Task.WhenAll(task1, task2, task3);

        foreach (string result in results)
        {
            Console.WriteLine(result);
        }
    }
}

Output

Output
Data 1
Data 2
Data 3

Task.WhenAny()

Task.WhenAny() completes when any one of the supplied Tasks completes.

C#
using System;
using System.Threading.Tasks;

namespace TaskExample
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Task task1 = Task.Delay(3000);
            Task task2 = Task.Delay(1000);

            Task completedTask =
                await Task.WhenAny(task1, task2);

            Console.WriteLine("One task has completed.");
        }
    }
}

This can be useful when the application needs to continue as soon as the first available operation finishes.

Exception Handling in Async Methods

Exceptions generated by an asynchronous operation can be handled using the normal try-catch mechanism around the awaited operation.

C#
using System;
 
class Program
{
    static async Task ProcessAsync()
    {
        await Task.Delay(500);

        throw new Exception("Something went wrong.");
    }

    static async Task Main()
    {
        try
        {
            await ProcessAsync();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

Output

Output
Something went wrong.

Common Mistakes in Async Programming

1. Using .Result unnecessarily

Avoid unnecessarily converting asynchronous code into synchronous waiting by using .Result or .Wait(). Prefer await when working with asynchronous APIs.

Avoid
var result = GetDataAsync().Result;
Prefer
var result = await GetDataAsync();

2. Using async without an asynchronous operation

Do not add async simply because it looks modern. The method should have a genuine asynchronous operation or return an appropriate Task-based result.

3. Forgetting await

If a Task-returning method is called without awaiting it, the program may continue before the operation has completed.

Async Does Not Automatically Mean Parallel

Asynchronous programming and parallel processing are related concepts, but they are not the same thing.

  • Asynchronous programming: focuses on not unnecessarily blocking while waiting for operations to complete.
  • Parallel processing: focuses on performing multiple pieces of work at the same time using available processing resources.
Important: Multithreading and Parallel Processing are covered separately in the next topics of Module 7.

Async Programming in ASP.NET Core

Asynchronous programming is widely used in ASP.NET Core applications for database access, HTTP requests, file operations, and other I/O-bound work.

C#
public async Task<IActionResult> GetStudents()
{
    var students = await _context.Students.ToListAsync();

    return View(students);
}

In this example, the controller action uses an asynchronous database operation. This approach is commonly used in real-world ASP.NET Core applications.

CIIT Practical Point: For .NET Full Stack development, students should understand async/await because database calls, Web API calls and external service calls are frequently asynchronous in production applications.

When Should You Use Asynchronous Programming?

Async programming is especially useful for:

  • Database operations
  • HTTP and REST API calls
  • File input/output
  • Network operations
  • Cloud service calls
  • External service integrations
  • Other operations where the application spends time waiting

Interview Questions

1. What is asynchronous programming?

Asynchronous programming allows an application to start an operation and continue execution without unnecessarily blocking while waiting for that operation to complete.

2. What is the use of async?

The async keyword allows a method to use await and work with Task-based asynchronous operations.

3. What is await?

await asynchronously waits for a Task to complete and then continues execution with the result.

4. What is Task?

Task represents an asynchronous operation.

5. Difference between Task and Task<T>?

Task represents an asynchronous operation without a result, while Task<T> represents an asynchronous operation that returns a value.

6. Does async create a new thread?

No. The async keyword itself does not create a new thread. Asynchronous programming and multithreading are different concepts.

7. What is Task.WhenAll()?

Task.WhenAll() allows multiple Tasks to be awaited together and completes after all supplied Tasks have completed.

Practice Programs

  1. Create an async method that waits for 2 seconds and prints a message.
  2. Create an async method that returns the sum of two numbers.
  3. Create an async method that returns a student name.
  4. Create three asynchronous methods and execute them using Task.WhenAll().
  5. Create an async method that simulates an API call using Task.Delay().
  6. Handle an exception generated inside an async method.

Summary

  • Asynchronous programming helps applications handle waiting operations efficiently.
  • C# uses async and await for asynchronous programming.
  • Task represents an asynchronous operation.
  • Task<T> represents an asynchronous operation that returns a value.
  • await asynchronously waits for a Task to complete.
  • Async programming is commonly used with APIs, databases, files and network operations.
  • Task.WhenAll() can be used when multiple asynchronous operations need to complete.
  • Asynchronous programming does not automatically mean multithreading or parallel processing.