Asynchronous Programming Basics in C# (async/await)
Learn async and await in C# to build responsive applications with asynchronous tasks, non-blocking code, and practical examples.
In modern applications, asynchronous programming is essential to keep the user interface responsive
and allow long-running operations to execute without freezing the application.
In C#, the async and await keywords are used for this purpose.
This structure allows long-running operations to run in the background while the application continues to respond.
What Is Asynchronous Programming?
Asynchronous programming allows other operations to continue while waiting for a task to complete. It is especially useful for I/O-bound operations such as file reading, network requests, database queries, or API calls.
// Synchronous: operations run sequentially
ReadFile();
SendData();
Console.WriteLine("Completed!");
// Asynchronous: operations continue without waiting
await ReadFileAsync();
await SendDataAsync();
Console.WriteLine("Completed!");
The async and await Keywords
The async keyword marks a method as asynchronous.
Inside such a method, the await keyword is used to wait for other asynchronous operations to complete.
This allows the thread to continue executing other tasks without being blocked.
async Task PerformOperationAsync()
{
Console.WriteLine("Operation started...");
await Task.Delay(2000); // waits 2 seconds but does not block the thread
Console.WriteLine("Operation completed!");
}
In the example above, even though Task.Delay introduces a delay, the application does not freeze.
The task waits in the background without blocking the CPU.
Task and Task<T> Types
Asynchronous methods typically return Task or Task<T>.
Task represents a void-returning asynchronous operation,
while Task<T> represents an asynchronous operation that returns a value.
// Asynchronous method without return value
async Task SaveFileAsync(string name)
{
await File.WriteAllTextAsync(name, "File content...");
}
// Asynchronous method that returns a result
async Task<string> GetDataAsync()
{
await Task.Delay(1000);
return "Data received from server";
}
// Usage:
string result = await GetDataAsync();
Console.WriteLine(result);
Asynchronous Main Method
Starting with C# 7.1, the Main method can also be asynchronous.
This allows direct use of await in console applications.
class Program
{
static async Task Main()
{
Console.WriteLine("Fetching data...");
string data = await GetDataAsync();
Console.WriteLine($"Result: {data}");
}
static async Task<string> GetDataAsync()
{
await Task.Delay(1500);
return "Completed!";
}
}
Important Notes When Using await
- await can only be used inside methods marked with
async. - Multiple asynchronous tasks can be started simultaneously (
Task.WhenAll()orTask.WhenAny()). async voidshould only be used for event handlers.
// Running multiple tasks in parallel
var task1 = ReadFileAsync();
var task2 = SendDataAsync();
await Task.WhenAll(task1, task2);
Console.WriteLine("Both tasks completed.");
Error Handling in Asynchronous Code
Errors in asynchronous methods can be handled using try-catch blocks.
If an awaited task throws an exception, the catch block will be executed.
try
{
await ReadFileAsync();
}
catch (IOException ex)
{
Console.WriteLine($"An error occurred while reading the file: {ex.Message}");
}
Example: Fetching Data from an API
The following example demonstrates an asynchronous HTTP request using HttpClient.
The await keyword prevents the application from freezing while waiting for the network response.
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
using HttpClient client = new HttpClient();
string url = "https://jsonplaceholder.typicode.com/posts/1";
Console.WriteLine("Requesting data...");
string json = await client.GetStringAsync(url);
Console.WriteLine("Received JSON:");
Console.WriteLine(json);
}
}
WPF Example: Asynchronous Operation and UI Update
In the following example, when a button is clicked in a WPF application,
a long-running operation (simulated with Task.Delay) is executed asynchronously.
Thanks to the await expression, the UI remains responsive during the operation,
and the TextBox content can be updated without freezing.
// MainWindow.xaml
<Window x:Class="AsyncExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Async / Await Example" Height="250" Width="400">
<Grid Margin="20">
<StackPanel>
<TextBlock Text="Asynchronous Operation Example" FontWeight="Bold" FontSize="16" Margin="0,0,0,10"/>
<TextBox x:Name="txtStatus" Height="30" Margin="0,0,0,10"
VerticalContentAlignment="Center" FontSize="14"/>
<Button x:Name="btnStart" Height="35" Content="Start Operation"
Click="btnStart_Click" FontSize="14"/>
</StackPanel>
</Grid>
</Window>
// MainWindow.xaml.cs
using System;
using System.Threading.Tasks;
using System.Windows;
namespace AsyncExample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
// async void is acceptable here because it's an event handler
txtStatus.Text = "Operation started...";
btnStart.IsEnabled = false; // disable button
// Simulate a long-running operation (e.g., file download)
await Task.Delay(3000);
txtStatus.Text = "Operation completed!";
btnStart.IsEnabled = true;
}
}
}
// Execution flow:
1. The user clicks the "Start Operation" button.
2. The TextBox is updated to "Operation started...".
3. A 3-second delay occurs (UI remains responsive).
4. Once complete, the TextBox changes to "Operation completed!".
Note: If this code were executed synchronously (using Thread.Sleep()),
the UI would freeze, and even the "Operation started..." message would not appear until completion.
Using await Task.Delay() completely eliminates this issue.
WPF Example: Updating TextBox from a Task (Using Dispatcher)
In this example, a long-running process runs inside a separate Task.
The txtStatus.Text updates are executed back on the UI Thread
using Dispatcher.Invoke.
This prevents cross-thread access errors.
// MainWindow.xaml
<Window x:Class="AsyncExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Dispatcher Example" Height="250" Width="400">
<Grid Margin="20">
<StackPanel>
<TextBlock Text="Updating UI Inside a Task" FontWeight="Bold" FontSize="16" Margin="0,0,0,10"/>
<TextBox x:Name="txtStatus" Height="30" Margin="0,0,0,10"
VerticalContentAlignment="Center" FontSize="14"/>
<Button x:Name="btnStart" Height="35" Content="Start Long Operation"
Click="btnStart_Click" FontSize="14"/>
</StackPanel>
</Grid>
</Window>
// MainWindow.xaml.cs
using System;
using System.Threading.Tasks;
using System.Windows;
namespace AsyncExample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
btnStart.IsEnabled = false;
txtStatus.Text = "Process started...";
// Run long operation inside a Task
await Task.Run(() =>
{
for (int i = 1; i <= 5; i++)
{
// Simulate work
Task.Delay(1000).Wait();
// UI update through Dispatcher
Dispatcher.Invoke(() =>
{
txtStatus.Text = $"Step {i} completed...";
});
}
});
txtStatus.Text = "All steps completed!";
btnStart.IsEnabled = true;
}
}
}
// Execution flow:
1. The "Start Long Operation" button is clicked.
2. A 5-step process begins inside Task.Run.
3. Each second, Dispatcher.Invoke updates the TextBox.
4. The UI remains responsive during each step.
Note:
If Dispatcher.Invoke() were replaced with a direct txtStatus.Text = ... assignment,
an exception would occur due to cross-thread access.
Alternatively, Dispatcher.BeginInvoke() can be used, which does not block the UI thread.
TL;DR (Summary)
asyncmakes a method asynchronous,awaitwaits for the operation to complete.TaskandTask<T>represent asynchronous operations and their results.awaitdoes not block the CPU; execution continues once the task completes.- Use
Task.WhenAll()to run multiple tasks simultaneously. - Handle exceptions with
try-catch;async voidshould be used only for event handlers.
Related Articles
Asynchronous Streams in C# (IAsyncEnumerable)
Learn asynchronous streams in C# with IAsyncEnumerable to process data step by step using modern async iteration patterns.
Process and Thread Management in C#
Learn process and thread management in C# to control execution flow, system resources, and multithreaded applications.
Task Parallel Library (TPL) and Parallel Programming in C#
Learn Task Parallel Library and parallel programming in C# using Task, Parallel, and concurrency patterns with practical examples.