How the async Keyword Works
Many beginners think that writing async on a method makes it run in the background or on a new thread. It does not. In this lesson we see exactly what async does and what it does not do.
What async does
The async keyword is an instruction for the compiler. It does four things:
- It allows
awaitinside the method. Withoutasync, the wordawaitgives a compile error. - It wraps your return value in a Task. You write
return 10;and the caller receives aTask<int>. - It stores exceptions inside the Task. An exception thrown in the method does not fly out immediately; it is kept in the returned task and re-thrown when someone awaits it.
- It tells the compiler to rewrite the method into a small "state machine" that can pause at an
awaitand continue later. (We look inside it in How Async Works Internally.)
What async does NOT do
- It does not create a new thread.
- It does not make the method run in the background.
- It does not make slow code faster.
An async method runs synchronously, on the caller's thread, until it reaches the first await of a task that is not finished yet. Only at that point does it pause and return to the caller.
Proof: watch the thread numbers
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine($"1. Main started on thread {Environment.CurrentManagedThreadId}");
Task task = DoWorkAsync();
Console.WriteLine("3. Main got the Task back and continues");
await task;
Console.WriteLine("5. Main finished");
}
static async Task DoWorkAsync()
{
Console.WriteLine($"2. DoWorkAsync started on thread {Environment.CurrentManagedThreadId}");
await Task.Delay(1000); // the method pauses here and returns to Main
Console.WriteLine($"4. DoWorkAsync resumed on thread {Environment.CurrentManagedThreadId}");
}
}
- Line 2 shows that
DoWorkAsyncstarted on the same thread asMain. No new thread was created. - Line 3 appears before line 4: at the
await,DoWorkAsyncpaused and handed aTaskback toMain. - Line 4 ran on a different thread. In a console app the code after
awaitcontinues on any free thread-pool thread. (In WinForms/WPF it would come back to the UI thread.)
An async method without await
If you write async but never use await, the compiler warns you:
static async Task<int> AddAsync(int a, int b)
{
return a + b;
}
This method is completely synchronous; async only added a little overhead. If you need to return a Task without any real waiting, return a completed task instead:
static Task<int> AddAsync(int a, int b)
{
return Task.FromResult(a + b); // no async keyword needed
}
The return value is wrapped for you
static async Task<int> GetStockAsync(int productId)
{
await Task.Delay(500); // pretend: read from the database
return 25; // we return int, the caller gets Task<int>
}
// Caller
Task<int> stockTask = GetStockAsync(101); // the Task
int stock = await stockTask; // the int inside it
Console.WriteLine($"Stock: {stock}");
For a method that returns Task (no value), you simply write return; or nothing at all.
Exceptions are stored in the Task
static async Task SaveOrderAsync()
{
await Task.Delay(100);
throw new InvalidOperationException("Database is not available");
}
static async Task Main()
{
Task saveTask = SaveOrderAsync(); // no exception yet
Console.WriteLine("Save started");
try
{
await saveTask; // the exception is thrown here
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
Why async void is dangerous
An async void method returns nothing, so there is no Task to store its exception. If it fails, the exception cannot be caught by the caller and can crash the whole application. The caller also can't know when it has finished.
// Bad: nobody can await this or catch its errors
async void SendEmail() { ... }
// Good
async Task SendEmailAsync() { ... }
// The only correct use of async void: event handlers
private async void btnSend_Click(object sender, EventArgs e)
{
try
{
await SendEmailAsync();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Where you can't use async
- Constructors can't be async. Use a static
CreateAsync()factory method instead. - Properties can't be async. Use a method such as
GetTotalAsync(). - Async methods can't have
ref,outorinparameters. - The
Mainmethod can be async only from C# 7.1 onwards.
Summary
asyncenablesawaitand makes the compiler build a state machine for the method.- It wraps the return value and any exception in the returned
Task. - It does not create a thread; the method runs synchronously until the first real
await. - An async method without
awaitgives warning CS1998 and runs synchronously. - Use
async Task, notasync void, except for event handlers.