The Task Parallel Library Sampler – Part 13: Async/Await

Previous Post in this series:
Part 12: Cancelling Threads with the CancellationTokenSource – The Code

This sample derives from a Microsoft example and an updated solution is available here.

AsyncAwaitSample model:

public class AsyncAwaitSample : Sample
{
	public override string SampleName
	{
		get { return "Async/Await Sample"; }
	}

	public override bool ImageRequired
	{
		get { return false; }
	}

	public async override void Run(System.Drawing.Bitmap bmp = null, Action<string> UpdateLog = null)
	{
		Stopwatch s = new Stopwatch();
		s.Start();

		UpdateLog("Step 1 (Run 1): Starting an await call to an asyncronous method.");
		int result = await AccessTheWebAsync("Run 1", UpdateLog);
		UpdateLog("Step 6 (Run 1): Done await call to an asyncronous method.");

		//this works and will even run asynchronously but won't wait on any result.
		//more then likely we will be long gone from this method before the method below is done
		UpdateLog(Environment.NewLine + "Step 1 (Run 2): Starting an async call without await and no result.");
		AccessTheWebAsync("Run 2", UpdateLog);
		UpdateLog("Step 6 (Run 2): Done with the async call without await and no result");

		s.Stop();
		RunTime = s.Elapsed;
	}

	// Three things to note in the signature: 
	//  - The method has an async modifier.  
	//  - The return type is Task or Task<T>. (See "Return Types" section.)
	//    Here, it is Task<int> because the return statement returns an integer. 
	//  - The method name ends in "Async."
	async Task<int> AccessTheWebAsync(string runDesignation, Action<string> UpdateLog)
	{
		// You need to add a reference to System.Net.Http to declare client.
		HttpClient client = new HttpClient();

		// GetStringAsync returns a Task<string>. That means that when you await the 
		// task you'll get a string (urlContents).
		// This also allows you set a lot of properties on the task rather then just running
		// it if you need to.
		Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
		UpdateLog("Step 2 (" + runDesignation+ "): Sleeping for ten seconds.");
		await Task.Delay(10000);
		UpdateLog("Step 3 (" + runDesignation+ "): Woke up.");
		UpdateLog("Step 4 (" + runDesignation+ "): Getting the web page.");
		// The await operator suspends AccessTheWebAsync. 
		//  - AccessTheWebAsync can't continue until getStringTask is complete. 
		//  - Meanwhile, control returns to the caller of AccessTheWebAsync. 
		//  - Control resumes here when getStringTask is complete.  
		//  - The await operator then retrieves the string result from getStringTask. 
		// This could also have been done as 
		// string urlContents = await client.GetStringAsync("http://msdn.microsoft.com");
		string urlContents = await getStringTask;
		UpdateLog("Step 5 (" + runDesignation+ "): Got the web page.");
		// The return statement specifies an integer result. 
		// Any methods that are awaiting AccessTheWebAsync retrieve the length value. 
		return urlContents.Length;
	}
}

There are two runs here, one shown using the keyword “await” with an asyncronous method and another run just running an asyncronous method without using “await”. It is very important to understand the conventions when using async. That is the name of the method should contain “async” in it. As an example, the HttpClient.GetStringAsync() makes it obvious that it is an async method. As shown in the comments, when calling an async method with await, the current thread calls the method and then waits for the method to return before continuing. If an asyncronous method is called without using await, a seperate thread is spun off and execution continues on. This can be very dangerous if there are results you’re waiting for from an asyncrounous method.

Run results:

Starting Async/Await Sample
Step 1 (Run 1): Starting an await call to an asyncronous method.
Step 2 (Run 1): Sleeping for ten seconds.
Completed Async/Await Sample
Async/Await Sample ran in 00:00:10.0173992

Step 3 (Run 1): Woke up.
Step 4 (Run 1): Getting the web page.
Step 5 (Run 1): Got the web page.
Step 6 (Run 1): Done await call to an asyncronous method.

Step 1 (Run 2): Starting an async call without await and no result.
Step 2 (Run 2): Sleeping for ten seconds.
Step 6 (Run 2): Done with the async call without await and no result
Step 3 (Run 2): Woke up.
Step 4 (Run 2): Getting the web page.
Step 5 (Run 2): Got the web page.

Looking at Run 1 you can see that AsyncAwaitSample.Run() is started, the first run is started and then we get the message that the sample is done. But why? Because we’re calling the run method, which is asynchronous, without await, down in the Sampler model. As such, the calling code calls the method, a thread gets spun up, and then execution continues in the original calling code. It never waits, just continues. So why define the method as “async”? Because the only way to use await with an asynchronous method is if the method itself is async. If you look at Run 2, it is even more obvious what happens when you call an async method without using await. Run 1, which uses await, runs the steps sequentially (other then the messages from Sampler model). Run 2 clearly does the steps out of order.

Another important point, the Run method is defined by a base class, as such the Sampler model is just calling run on all it’s samples. But what is interesting is that we are able to make Run into an async method, which we have to do to use await, and we are given no warning from Sampler that we may be calling an async method without using await. Now, in AsyncAwaitSample we are given a warning that for Run 2 we are calling an async method without await. But there it is more obvious that we are doing so.

In the first post in this series I said there would be 15 posts but it looks like there will be only 14. The next post will be a wrap-up/final to this series.

Thanks,
Brian

Leave a Reply