Over the years there have been multiple ways to write asynchronous code in .NET with async/await
being the latest attempt to make it less error-prone and more developer friendly.
We started the asynchronous journey in .NET 1.0 with the Asynchronous Programming Model where you had to write a Begin
and End
method following a specific pattern:
class Handler | |
{ | |
public int DoStuff(string arg); | |
public IAsyncResult BeginDoStuff(string arg, AsyncCallback? callback, object? state); | |
public int EndDoStuff(IAsyncResult asyncResult); | |
} |
This was improved in .NET 2.0 with the introduction of the Event-Based Asynchronous pattern(focussing mainly on client applications) in combination with the SynchronizationContext
simplifying the scheduling of work on the UI thread:
class Handler | |
{ | |
public int DoStuff(string arg); | |
public void DoStuffAsync(string arg, object? userToken); | |
public event DoStuffEventHandler? DoStuffCompleted; | |
} | |
public delegate void DoStuffEventHandler(object sender, DoStuffEventArgs e); |
In .NET 4.0 the System.Threading.Tasks.Task
type was introduced, a data structure that represents the eventual completion of some asynchronous operation:
Action<object> action = (object obj) => | |
{ | |
Console.WriteLine("Task={0}, obj={1}, Thread={2}", | |
Task.CurrentId, obj, | |
Thread.CurrentThread.ManagedThreadId); | |
}; | |
// Create a task but do not start it. | |
Task t1 = new Task(action, "alpha"); | |
// Launch t1 | |
t1.Start(); |
And finally we arrived at async/await
where we use the power of iterators to generate a state machine that handles all the continuations and callbacks for us. This allows us to write asynchronous code in a way that almost feel as synchronous simplifying the mental model and readability of our code:
static async Task Main(string[] args) | |
{ | |
Coffee cup = PourCoffee(); | |
Console.WriteLine("coffee is ready"); | |
var eggsTask = FryEggsAsync(2); | |
var baconTask = FryBaconAsync(3); | |
var toastTask = MakeToastWithButterAndJamAsync(2); | |
var eggs = await eggsTask; | |
Console.WriteLine("eggs are ready"); | |
var bacon = await baconTask; | |
Console.WriteLine("bacon is ready"); | |
var toast = await toastTask; | |
Console.WriteLine("toast is ready"); | |
Juice oj = PourOJ(); | |
Console.WriteLine("oj is ready"); | |
Console.WriteLine("Breakfast is ready!"); | |
} |
If you want to learn about all the details, check out the amazingly detailed post by Stephen Toub: How Async/Await Really Works in C# - .NET Blog (microsoft.com)
More information: Asynchronous programming - C# | Microsoft Learn