A little further down the list of top programming languages, the Async keyword popped up. This didn’t surprise me as asynchronous programming is critical in modern programming. This runs through how to use Await and Async in C#.
These asynchronous methods are fantastic as they allow you to execute multiple sets of code in parallel. This can have a massive impact on performance and help significantly improve user experience. The Await and Async keywords are very powerful C# components.
Table of Contents
Why asynchronous programming?
Imagine if we have a simple piece of code such as the below.
public void Run() { BreakfastBase bacon = new Bacon(); Console.WriteLine( bacon.Cook()); BreakfastBase eggs = new Eggs(); Console.WriteLine(eggs.Cook()); } } class Bacon: BreakfastBase { public override string Cook() { Thread.Sleep(5000); return "Bacon is done"; } } class Eggs: BreakfastBase { public override string Cook() { Thread.Sleep(6000); return "Eggs are done"; } } abstract class BreakfastBase { public abstract string Cook(); }
In the above code, we have two types of BreakfastBase. These are Eggs and Bacon. Each of them will make the thread sleep for 6 seconds, and 5 seconds respectively. This means that when we call the “Run” method a new instance of Bacon is created, the Bacon is cooked for 5 seconds and then the Eggs are cooked for a further 6 seconds. Effectively, this has led to a total run time of 11 seconds because we’re running each task synchronously. This is where the async keyword comes in.
Await and Async in C# tasks
To run the tasks in parallel we want to make them asynchronous methods. The easiest way of making a block of code Async is to wrap it in a new task. This can be seen here:
cookIngredientAsync(BreakfastIngredient ingredient) { return await Task.Run(() => ingredient.Cook()); }
The arrow function (” () => “) is used to transform the method into an action that can be executed as a task. This task will now run asynchronously when used with the await keyword. The await keyword can only be used in an async method, and an async method should always return a Task of some sort. Awesomely, this code wraps a method inside of a Task ready to be run at a later point.
Running Async Tasks
The above method will now run the methods as async calls and return a Task<string> when the Task completes.
Console.WriteLine(await cookIngredientAsync(ingredient)); ingredient = new Eggs(); Console.WriteLine(await cookIngredientAsync(ingredient));
So now we have to wait (using await) for the async Task to complete prior to printing the final string.
This is fantastic, but effectively we’ve got to a point where we’re still just running the async tasks one after the other. What’s a good way of actually running them all together?
Running in parallel
In the above code, we now have two distinct tasks that we’re running sequentially. The next code will be looking at moving those tasks into a list of tasks, prior to running through them until they’ve all been completed.
BreakfastIngredient ingredient = new Bacon(); var task1 = cookIngredientAsync(ingredient); ingredient = new Eggs(); var task2 = cookIngredientAsync(ingredient); var taskList = new List<Task<string>>() { task1, task2 }; while(taskList.Count > 0 ) { var finishedTask = await Task.WhenAny<string>(taskList); Console.WriteLine(await finishedTask); taskList.Remove(finishedTask); }
As you can see, we’ve removed the await from the Tasks. This prevents the result from the awaitable task being requested sequentially. We can then add these tasks to a list. On completion of any task “Task.WhenAny” populates the finishedTask variable with that task. This allows us to print the awaited string prior to removing the Task from the taskList.
This results in both of the Tasks running in parallel and our breakfast getting cooked in 6 seconds rather than the 11 seconds it took to run sequentially. Result!
You can read up more about async programming from the pro’s at https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
I got caught with this too, “As you can see, we’ve removed the await from the Tasks. This prevents either task from running immediately.”
All tasks, executed in this matter, will be “hot”, meaning that they will be scheduled and will execute right away… which doesn’t always appear in the debugger… why… because it executes from thread pool, and the debugger is held on one thread. Hit F10, and you might just hit one fo those tasks, and start jumping around driving you nuts…
the await does just that.. halts the execution, enters a state machine, releases the current thread, does a bunch of work, and then returns after the task has been completed.
Task.WhenAny, Task.WhenAll do NOT start the threads, they just wait for one or all threads to be “awaited”.
Good point, I think I oversimplified and you’re right in saying the Await doesn’t run the method, but purely waits for the result. I’ve edited it to clarify in the post. Thanks for your input!