In A Brief Introduction to Fundamental OOP, I built a small twitter application. It does the job well, but it has a very undesirable quality. When you click the search button, the application momentarily freezes as it waits for the Twitter service to respond. The application would feel a lot smoother if it allowed the user to continue using the application while the request is processed.
Here is the button click handler.
private void SearchButton_Click(object sender, RoutedEventArgs e)
{
SocialMediaReader reader = new Twitter(SearchText.Text);
listBox.ItemsSource = reader.GetMessages();
}
I’m calling GetMessages() to assign the ItemSource property of listBox. This is the call that is blocking the UI thread.
public abstract class SocialMediaReader
{
protected abstract Uri GetUri();
protected abstract IEnumerable<Message> ParseData(string data);
public IEnumerable<Message> GetMessages()
{
WebClient client = new WebClient();
string data = client.DownloadString(GetUri());
return ParseData(data);
}
}
As you can see the GetMessages() method simply returns an IEnumerable<Message>. The blocking call is to to WebClient.DownloadString().
Callbacks
When you “fire and forget” a method, it is essential to define what comes next. The asynchronous version of GetMessages will accept a delegate that will be called when the messages are received. This is similar to your typical continuation, except it only affects control flow when data is available.
public void GetMessages(Action<IEnumerable<Message>> callback)
{
WebClient client = new WebClient();
client.DownloadStringCompleted += (s, e) =>
callback(e.Error == null ? ParseData(e.Result) : Enumerable.Empty<Message>());
client.DownloadStringAsync(GetUri());
}
Instead of calling DownloadString, I call DownloadStringAsync, which will call client.DownloadStringCompleted when it is finished. The event handler for client.DownloadStringCompleted is a lambda expression that executes the callback.
private void SearchButton_Click(object sender, RoutedEventArgs e)
{
SocialMediaReader reader = new Twitter(SearchText.Text);
reader.GetMessages(messages => listBox.ItemsSource = messages);
}
The click event now calls the asynchronous version of GetMessages by passing a lambda expression as an argument. This expression simply assigns listBox.ItemsSource with the data that was parsed from Twitter.
C# 5
The Microsoft Visual Studio Async CTP adds language constructs to make asynchronous programming more expressive. After you install the CTP, you need to reference AsyncCtpLibrary.dll in your project. This is typically available at %HOMEPATH%\Documents\Microsoft Visual Studio Async CTP\Samples.
Telerik JustCode Q2 SP1 supports the new language constructs, and I’ve written an article describing async support in Telerik JustCode. If you need a Visual Studio productivity tool, you can count on JustCode to be up-to-date.
Two new keywords are required to enable an asynchronous method. The first is the async keyword, which is used to mark the method signature as asynchronous. The second is the await keyword. The line that uses the await keyword is called immediately, and the lines after the await keyword are part of the continuation. If a value is returned from the method, the assignment because part of the continuation as well.
public async Task<IEnumerable<Message>> GetMessagesAsync()
{
WebClient client = new WebClient();
string data = await client.DownloadStringTaskAsync(GetUri());
return ParseData(data);
}
private async void SearchButton_Click(object sender, RoutedEventArgs e)
{
SocialMediaReader reader = new Twitter(SearchText.Text);
listBox.ItemsSource = await reader.GetMessagesAsync();
}
To maintain the non-asynchronous version of the method, I had to change the name of the name of the async version to GetMessagesAsync. The parameter list was identical (no parameters), but they have different return types. You cannot overload on return type. Mentioning return type, you may have noticed the GetMessageAsync method returns Task<IEnumerable<Messsage>> rather than IEnumerable<Message>. Methods marked async must be void, return Task (equivalent to using void), or return Task<T>. When the return value is Task<T>, you return from the method the type defined as T. For void and Task, you do not return a result.
As you can see, this is somewhat cleaner than the callback version of the code. It eliminates the requirement to pass around lambda expressions or delegates.
Conclusion
In the introduction, I talked about the issue of the UI thread being blocked. Using asynchronous methods solves the problem and creates a more responsive environment.