Writing multithreaded programs using Async & Await in C#


The biggest feature in C# and Visual Basic languages in Visual Studio 11 is inclusion of first class language support for asynchrony. Writing multi-threaded programs that scale, are easily modifiable, maintainable and understandable by more than one person (i.e. the one that wrote the code) is hard. This is certainly something I have seen people get wrong time and time again.

Probably the most dangerous word to use with undergraduate or even postgraduates is the word “Thread”. When developing software systems, this is certainly an area that requires somebody experienced, and proactive enough to ensure that developers are monitored when they are assigned tasks using threading, as things can get out of hand very quickly.. I have always tried to ensure that developers refrain from using the Thread class and suggesting they use the ThreadPool  and its QueueUserWorkItem  method or the background worker component where possible, but there are times when you have to implement the IAsyncResult design pattern, which makes understanding what your normally synchronous application (and mind-set) hard, because there is an Inversion of Control in the code that is executing, requiring you use a WaitCallBack and WaitHandles and ManualResetEvent and AutoResetEvent classes.

As we move forward, it is more important that developers are as productive as possible, especially in a world where devices are proliferating at the rate that they are. I can now program a Computer, Mobile Phone or Tablet, where connectivity, and updating is a real issue on tablets and phones, so being able to write robust, multi-threaded code quickly and relatively bug free increases in importance.

Note that I am using WPF in this example, if you would like to know how to do this in WinRT/Metro, then have a look at this example.

Single Threaded Example

If you run Visual Studio and create a new WPF application and call the project AsyncWpfApplication

NewProject

This synchronous application application is going to process a list of URLs, and calculate the size of the pages so please ensure you add a reference to System.Net.Http

SystemNet

Add the following XAML markup

<Window x:Class="AsyncWpfApplication.MainWindow"
Title="MainWindow" Height="400" Width="600">
<Grid>
 
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
 
<TextBox Margin="20" Grid.Row="0" HorizontalAlignment="Left" TextWrapping="Wrap" FontFamily="Lucida Console" x:Name="resultsTextBox" Height="250" Width="500" />
<Button Grid.Row="1" HorizontalAlignment="Right" Margin="0,0,70,20" Content="Start" x:Name="startButton" Height="22" Width="75" Click="startButton_Click_1" />
 
</Grid>
</Window>
 

Add the following in the code behind

using System;
using System.Collections.Generic;
using System.Windows;
using System.Net.Http;
using System.Net;
using System.IO;
using System.Threading.Tasks;
 
namespace AsyncWpfApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
 
private void startButton_Click_1(object sender, RoutedEventArgs e)
{
resultsTextBox.Clear();
SumPageSizes();
resultsTextBox.Text += "\r\nControl returned to startButton_Click.";
}
 
private void SumPageSizes()
{
// Make a list of web addresses.
IEnumerable<string> urlList = SetUpURLList();
 
var total = 0;
foreach (var url in urlList)
{
// GetURLContents returns the contents of url as a byte array.
byte[] urlContents = GetURLContents(url);
 
DisplayResults(url, urlContents);
 
// Update the total.
total += urlContents.Length;
}
 
// Display the total count for all of the web addresses.
resultsTextBox.Text += string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);
}
 
 
private IEnumerable<string> SetUpURLList()
{
var urls = new List<string>
{
};
return urls;
}
 
 
private byte[] GetURLContents(string url)
{
// The downloaded resource ends up in the variable named content.
var content = new MemoryStream();
 
// Initialize an HttpWebRequest for the current URL.
var webReq = (HttpWebRequest)WebRequest.Create(url);
 
// Send the request to the Internet resource and wait for
// the response.
using (var response = webReq.GetResponse())
{
// Get the data stream that is associated with the specified URL.
using (Stream responseStream = response.GetResponseStream())
{
// Read the bytes in responseStream and copy them to content. 
responseStream.CopyTo(content);
}
}
 
// Return the result as a byte array.
return content.ToArray();
}
 
private void DisplayResults(string url, byte[] content)
{
// Display the length of each website. The string format
// is designed to be used with a monospaced font, such as
// Lucida Console or Global Monospace.
var bytes = content.Length;
// Strip off the "http://&quot;.
var displayURL = url.Replace("http://&quot;, "");
resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);
}
}
}
 

If you then hit F5 to run the application and press start you will find that the start button sticks and the application itself is unresponsive because of all the work that is blocking the main UI thread. After a few moment you should get the following

Screen

Multithreaded Example

When retrofitting the new asyc language features you need to be aware of the following

  1. The new asyc language capabilities are based around Task<T> found in System.Threading.Tasks.Task<TResult>. Knowing how that class works with increase the ease of you ability to use the new asyc features
  2. Any method that uses the new asynchronous functionality must use the new keyword asyc.
  3. There are a plethora of new classes in the .NET framework that are suffixed with Asyc that return a Task<T> that you can use with the new await keyword.
using System;
using System.Collections.Generic;
using System.Windows;
using System.Net.Http;
using System.Net;
using System.IO;
using System.Threading.Tasks;
 
namespace AsyncWpfApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
 
private async void startButton_Click_1(object sender, RoutedEventArgs e)
{
resultsTextBox.Clear();
await SumPageSizesAsync();
resultsTextBox.Text += "\r\nControl returned to startButton_Click.";
}
 
private async Task SumPageSizesAsync()
{
// Make a list of web addresses.
IEnumerable<string> urlList = SetUpURLList();
 
var total = 0;
 
foreach (var url in urlList)
{
// GetURLContentsAsync returns a Task<T>. At completion, the task
// produces a byte array.
Task<byte[]> getContentsTask = GetURLContentsAsync(url);
byte[] urlContents = await getContentsTask;
 
// The following line can replace the previous two assignment statements.
//byte[] urlContents = await GetURLContentsAsync(url);
 
DisplayResults(url, urlContents);
 
// Update the total.         
total += urlContents.Length;
}
 
// Display the total count for all of the websites.
resultsTextBox.Text +=
string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);
}
 
 
 
 
private IEnumerable<string> SetUpURLList()
{
var urls = new List<string>
{
};
return urls;
}
 
private async Task<byte[]> GetURLContentsAsync(string url)
{
// The downloaded resource ends up in the variable named content.
var content = new MemoryStream();
 
// Initialize an HttpWebRequest for the current URL.
var webReq = (HttpWebRequest)WebRequest.Create(url);
 
// Send the request to the Internet resource and wait for
// the response.
Task<WebResponse> responseTask = webReq.GetResponseAsync();
using (WebResponse response = await responseTask)
{
// The following line can replace the previous two lines.
//using (WebResponse response = await webReq.GetResponseAsync())
 
// Get the data stream that is associated with the specified url.
using (Stream responseStream = response.GetResponseStream())
{
// Read the bytes in responseStream and copy them to content.
// CopyToAsync returns a Task, not a Task<T>.
Task copyTask = responseStream.CopyToAsync(content);
 
// When copyTask is completed, content contains a copy of
// responseStream.
await copyTask;
 
// The following line can replace the previous two statements.
//await responseStream.CopyToAsync(content);
}
}
 
// Return the result as a byte array.
return content.ToArray();
}
 
 
 
private void DisplayResults(string url, byte[] content)
{
// Display the length of each website. The string format
// is designed to be used with a monospaced font, such as
// Lucida Console or Global Monospace.
var bytes = content.Length;
// Strip off the "http://&quot;.
var displayURL = url.Replace("http://&quot;, "");
resultsTextBox.Text += string.Format("\n{0,-58} {1,8}", displayURL, bytes);
}
}
}

    If you examine the retrofitted example above you will see that the synchronous methods have indeed been replaced with their methods that are suffixed with Async e.g. SomeMethodAsyc and all you have to do is use the await keyword until the asynchronous task completes. The C# language team have really made asynchronous programming “easy peasy lemon squeezy”.

7 thoughts on “Writing multithreaded programs using Async & Await in C#

  1. Your example is not really async, since you request a Task and then wait for its completion right away. This way each request is sent out one after the other (and this shows in the printout: you get the same files in the same order). Instead you should have collected all the tasks in a collection and await on all of them after you have started all of them. Then you have concurrent processing.

  2. I think you would do better to compile the souce code and run the code, as opposed to commenting on the code as a spectator. You will find immediately that the UI thread does not get locked as in the second example but does in the first. Also are you suggesting that my use of the await keyword is used sychronously?

  3. Hello,
    could you please explain to me what’s the meaning of

    // GetURLContentsAsync returns a Task. At completion, the task
    // produces a byte array.
    Task getContentsTask = GetURLContentsAsync(url);
    byte[] urlContents = await getContentsTask;

    ?

    Shouldn’t you domething that is task independent between the first row and the second row to make this code actaully effective?

    Thanks,
    Paolo

  4. I think you would do better to compile the souce code and run the code, as opposed to commenting on the code as a spectator. Debugging the code will answer your question.

  5. An that’s what i’m trying to do (without success, getting an exception), teaching something to people who actually take the time to study your code a bit would be nice too instead of just saying “run the code” (which is correct anyway)

  6. Ok i guess i understood what’s the catch, you were not trying to achieve any parallelism or anything, and that “something that is task independent” which i was referring to is the UI which keeps running.

    This should answer Miklos Maroti from the first post too.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s