Deceptive simplicity of async and await

January 23, 2012
.NET async C# WinRT
 

By introducing the new async/await keywords in C# 5 and by adding asynchronous APIs in .NET 4.5, Microsoft significantly lowered the bar for entering into the realm of the asynchronous programming. However, while presenting the new features of the upcoming .NET Framework release, answering the questions and helping to resolve the first issues I came to realization that this bar might be set too low.

These new additions help us to create asynchronously running methods using the familiar synchronous development model. We don’t need to segment our code to extract the parts to be executed in a separate thread or to be a callback. We don’t have to think about the threads or tasks anymore – just add await and the rest is done automatically.

That’s cool – I really like these new features and I believe they can simplify our lives, but only when used properly. Current development stack (Win7/.NET Framework 4) does not enforce the asynchronous development model. The async functionality can be introduced into existing synchronous application once the developer has fully realized the advantages of the async APIs and has mastered their proper usage.

WinRT and .NET 4.5 are different - this stack forces us to use async model. Again, this is not a bad thing but it requires some discipline and some preliminary knowledge. Right now many developers are new to the field and are experiencing the following:

Developer: “I want to use this function to read some data”
Compiler: “Good, but you may want to use await to get the string”
Developer: “OK, await is added. Can I get the string?”
Compiler: “Sure, just add async in the method header”
Developer: “Added. F5!”
Developer: “Hey, it does not work! Where is my string?”

The code looks good, it can be compiled but does not work. Why? Here are two most common mistakes I have seen so far.

Example 1

WriteFileAsync function definition:

async public void WriteFileAsync(string filename, string contents)
{
    var localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    var file = await localFolder.CreateFileAsync(filename, 
                           Windows.Storage.CreationCollisionOption.ReplaceExisting);
    var fs = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);     
    //...
}

And this function is called as

WriteFileAsync("FileName", "Some Text");

In some cases this code will work properly. But only in some – if there is a code that depends on the file content or tries to access the same file and this code is called after WriteFileAsync(), the developer will be faced with an exception or unpredicted results.

The problem here is that the developer use awaits for the system calls but WriteFileAsync by itself is not awaitable and any code following after WriteFileAsync() call will be executed before all the expected saving operations are done. To fix the issue the method should be modified

async public Task WriteFileAsync(string filename, string contents)
{
    //...
}

And the function should be called as

await WriteFileAsync("FileName", "Some Text");

Example 2

To implement Windows 8 Sharing contract, an application should handle DataTransferManager.DataRequested event. Below you can see an example of such a handler:

async void DataRequestedHandler(DataTransferManager sender, DataRequestedEventArgs args)
{
    _fileStream = await _lastSelectedFile.OpenAsync(FileAccessMode.Read);
	
    args.Request.Data.Properties.Title = "Data Title";
    args.Request.Data.SetBitmap(_fileStream);
}

Not surprisingly, this sample does not work either. DataTransferManager does not expect that handler is asynchronous and when the await keyword returns control to the manager, the latter sees an empty data request object, assumes that the contract is not properly implemented and will not show the application on the Share panel. The fix is easy, we just need to move OpenAsync out of the method and to remove async keyword from the header, but this example demonstrates another kind of side effects – we should be very careful with the async functions when some data are expected to be returned to the external code.

These mistakes look trivial but chances are they will appear quite often due to deceptive simplicity of the new syntax. Hopefully, the next version of Visual Studio and/or ReSharper will be able to detect such situations and warn about them.

What is your opinion? Do you see a problem here?

blog comments powered by Disqus