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.
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.
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>.
Task DoWorkAsync()
{
// asynchronous work
}
When the asynchronous operation does not return a value, Task is generally used.
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.
async Task DoWorkAsync()
{
// asynchronous code
}
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.
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
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
Program started
Async operation completed
Program finished
Explanation
- The program prints Program started.
Task.Delay(2000)represents an asynchronous delay.awaitwaits for the Task to complete.- After two seconds, the next statement executes.
- The program prints Program finished.
Example 2: Async Method Without Return Value
An asynchronous method that does not return a result normally uses Task.
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
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>.
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
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.
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);
}
}
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.
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.
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
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.
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
Data 1
Data 2
Data 3
Task.WhenAny()
Task.WhenAny() completes when any one of the supplied Tasks completes.
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.
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
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.
var result = GetDataAsync().Result;
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.
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.
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.
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
- Create an async method that waits for 2 seconds and prints a message.
- Create an async method that returns the sum of two numbers.
- Create an async method that returns a student name.
- Create three asynchronous methods and execute them using Task.WhenAll().
- Create an async method that simulates an API call using Task.Delay().
- 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.