Welcome back to a new C# tutorial where we will learn about parallel programming in C# using the async and await keywords !.
Even if you are not a multitasker believe me multitasking in programming is VERY important : ).
So let’s jump right into it!.
The Aysnc and Await Keywords
First, we need to learn about a couple of keywords in C#.
The async modifier is used to specify that a method is asynchronous.
The await operator suspends evaluation of the async method until the asynchronous operation completes.
Async tasks can be used when we have operations that do not depend on each other. For example, load a file locally and download a file online. This scenario is similar to when you have an app that will load the user data locally and download some updates at the same time.
Preparing our Project
So let’s consider the following file :
░░░░░░░░░▄░░░░░░░░░░░░░░▄░░░░ ░░░░░░░░▌▒█░░░░░░░░░░░▄▀▒▌░░░ ░░░░░░░░▌▒▒█░░░░░░░░▄▀▒▒▒▐░░░ ░░░░░░░▐▄▀▒▒▀▀▀▀▄▄▄▀▒▒▒▒▒▐░░░ ░░░░░▄▄▀▒░▒▒▒▒▒▒▒▒▒█▒▒▄█▒▐░░░ ░░░▄▀▒▒▒░░░▒▒▒░░░▒▒▒▀██▀▒▌░░░ ░░▐▒▒▒▄▄▒▒▒▒░░░▒▒▒▒▒▒▒▀▄▒▒▌░░ ░░▌░░▌█▀▒▒▒▒▒▄▀█▄▒▒▒▒▒▒▒█▒▐░░ ░▐░░░▒▒▒▒▒▒▒▒▌██▀▒▒░░░▒▒▒▀▄▌░ ░▌░▒▄██▄▒▒▒▒▒▒▒▒▒░░░░░░▒▒▒▒▌░ ▀▒▀▐▄█▄█▌▄░▀▒▒░░░░░░░░░░▒▒▒▐░ ▐▒▒▐▀▐▀▒░▄▄▒▄▒▒▒▒▒▒░▒░▒░▒▒▒▒▌ ▐▒▒▒▀▀▄▄▒▒▒▄▒▒▒▒▒▒▒▒░▒░▒░▒▒▐░ ░▌▒▒▒▒▒▒▀▀▀▒▒▒▒▒▒░▒░▒░▒░▒▒▒▌░ ░▐▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒░▒░▒▒▄▒▒▐░░ ░░▀▄▒▒▒▒▒▒▒▒▒▒▒░▒░▒░▒▄▒▒▒▒▌░░ ░░░░▀▄▒▒▒▒▒▒▒▒▒▒▄▄▄▀▒▒▒▒▄▀░░░ ░░░░░░▀▄▄▄▄▄▄▀▀▀▒▒▒▒▒▄▄▀░░░░░ ░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▀▀░░░░░░░░
Let’s place it in the same directory of our exe that we get from building our console app.
This way we don’t need to use the absolute path of the file to access it.
IO Bound Async Operation
First, let’s add the IO namespace to use the File class in C#.
using System.IO;
The file class contains an async method called ReadAllTextAync which we can await to get the text file content as a string.
So let’s add the tasks namespace to write async tasks.
using System.Threading.Tasks;
Now let’s define a new async method that will await the task of loading a file locally.
static async Task SummonDogLocally() {
Console.WriteLine("1. Summoning Dog Locally ...");
//read all the text inside the dog.txt async
string dogText = await File.ReadAllTextAsync("dog.txt");
//display the data inside the txt file
Console.WriteLine($"2. Dog Summoned Locally \\n{dogText}");
}
Since this is an async method, we need to return a Task. returning a task like this is equivalent to a void method since we are printing the content of the file directly inside the method.
If we would to actually return the string to the main method then we would return Task<string>, similar to the ReadAllTtextAsync method if we hover over it.
The await operator will do the magic of unwrapping the string result from the task and return it to us.
Network Bound Async Operation
Cool, now let’s write our second task which is to load a string from a URL online.
Let’s add the HTTP namespace so we can send HTTP requests.
using System.Net.Http;
an HTTP client object contains a method called GetStringAsync which is an async method that will read the content of a webpage as a string, so let’s use it : ).
static async Task SummonDogFromURL(string URL) //A Task return type will eventually yield a void { Console.WriteLine("1. Summoning Dog from URL ..."); //creating a new httpClient object inside a using clause, this will make sure that the httpClient object is disposed after we are done with it using (var httpClient = new HttpClient()) { //get the data from our URL in an async manner and store the results in a string string result = await httpClient.GetStringAsync(URL); //execution pauses here while awaiting GetStringAsync to complete //From this line and below, the execution will resume once the above awaitable is done //using await keyword, it will do the magic of unwrapping the Task<string> into string (result variable) Console.WriteLine($"2. Dog Summoned from URL \\n{result}"); } }
Now in the main method let’s start both of these tasks at the same time and see which of them finishes first.
First, let’s define a string called URL which is the link to a file on a GitHub repo I created.
Link to the text file on GitHub
https://raw.githubusercontent.com/l3oxer/Doggo/main/README.md
string URL = "https://raw.githubusercontent.com/l3oxer/Doggo/main/README.md";
Next, let’s define a new list of tasks that will take both of our async methods as elements since our async methods are returning a task.
//define a list of tasks that we want to do in an async manner var tasks = new List<Task> { SummonDogLocally(), SummonDogFromURL(URL) };
WhenAll
Now before we display that both of these tasks are done we need to await for them to be done inside the main method, this can be achieved using a static method from the Tasks class called WhenAll
await Task.WhenAll(tasks);
Since we are awaiting a task in the main method, we need to mark it as an async.
//here we marked the main as an async method static async Task Main(string[] args) { //the url from the github repo that contains the data we want to download string URL = "https://raw.githubusercontent.com/l3oxer/Doggo/main/README.md"; //define a list of tasks that we want to do in an async manner var tasks = new List<Task> { SummonDogLocally(), SummonDogFromURL(URL) }; //wait until all tasks are done await Task.WhenAll(tasks); //pause Console.ReadLine(); }
Actually, before we display the tasks are done message let’s also display the time it took to finish both of these tasks at the same time using an instance of the Stopwatch class.
First, let’s add the Diagnostics namespace.
using System.Diagnostics;
Then before we start the tasks we will start the stopwatch and after the tasks are done we are going to display the time.
//here we marked the main as an async method static async Task Main(string[] args) { //the url from the github repo that contains the data we want to download string URL = "https://raw.githubusercontent.com/l3oxer/Doggo/main/README.md"; //defining a new stopwatch to count the time span for the execution Stopwatch sw = new Stopwatch(); //start counting sw.Start(); //define a list of tasks that we want to do in an async manner var tasks = new List<Task> { SummonDogLocally(), SummonDogFromURL(URL) }; //wait until all tasks are done await Task.WhenAll(tasks); //stop the timmer sw.Stop(); //display the time Console.WriteLine("We are done here.... " + sw.Elapsed.TotalSeconds); //pause Console.ReadLine(); }
Now let’s run the application several times.
If your internet is slow enough you will see the dog summoned from the URL appear after the dog summoned from the local file.
Just to prove that both of these tasks are running at the same time let’s slow down the SummonDogLocally Method using Thread.Sleep().
So first let’s import the Threading namespace.
using System.Threading;
And in the SummonDogLocally method, we will wait for a full second before we finish the task.
static async Task SummonDogLocally() {
Console.WriteLine("1. Summoning Dog Locally ...");
//read all the text inside the dog.txt async
string dogText = await File.ReadAllTextAsync("dog.txt");
//sleep for 1 second just to verify that both tasks run at the same time
Thread.Sleep(1000);
//display the data inside the txt file
Console.WriteLine($"2. Dog Summoned Locally \\n{dogText}");
}
Now let’s run the app again.
Now we can see that the dog from the URL appeared before the dog from the file!.
So this is how async and await are used in C#, I really hope that you enjoyed this video. As a task for you try to write the same program synchronously without the await and async keywords and use the Stopwatch class to measure the time and compare it with the async approach : ).
Lecture Code
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace AsyncAwaitLecture{ class Program { //here we marked the main as an async method static async Task Main(string[] args) { //the url from the github repo that contains the data we want to download string URL = "https://raw.githubusercontent.com/l3oxer/Doggo/main/README.md"; //defining a new stopwatch to count the time span for the execution Stopwatch sw = new Stopwatch(); //start counting sw.Start(); //define a list of tasks that we want to do in an async manner var tasks = new List<Task> { SummonDogLocally(), SummonDogFromURL(URL) }; //wait until all tasks are done await Task.WhenAll(tasks); //stop the timmer sw.Stop(); //display the time Console.WriteLine("We are done here.... " + sw.Elapsed.TotalSeconds); Console.ReadLine(); } static async Task SummonDogFromURL(string URL) //A Task return type will eventually yield a void { Console.WriteLine("1. Summoning Dog from URL ..."); //creating a new httpClient object inside a using clause, this will make sure that the httpClient object is disposed after we are done with it using (var httpClient = new HttpClient()) { //get the data from our URL in an async manner and store the results in a string string result = await httpClient.GetStringAsync(URL); //execution pauses here while awaiting GetStringAsync to complete //From this line and below, the execution will resume once the above awaitable is done //using await keyword, it will do the magic of unwrapping the Task<string> into string (result variable) Console.WriteLine($"2. Dog Summoned from URL \\n{result}"); } // we are awaiting the Async Method GetStringAsync } static async Task SummonDogLocally() { Console.WriteLine("1. Summoning Dog Locally ..."); //read all the text inside the dog.txt async string dogText = await File.ReadAllTextAsync("dog.txt"); //sleep for 1 second just to verify that both tasks run at the same time Thread.Sleep(1000); //display the data inside the txt file Console.WriteLine($"2. Dog Summoned Locally \\n{dogText}"); } } }