C# 5.0: await and Reentrancy

January 28, 2013

The relatively new async/await keywords in C# 5.0 are truly great. I’ve been using them for a while now, and I always contrast these to the way things can be done in C++11; and even with the help of PPL tasks – it stands out as clearly victorious, with its ease of use and lack of verbosity.

In fact, it’s so easy to use that I find myself creating new methods that are mostly “Async”, even if the benefit may not be that great – just because it’s easy to do.

For example, suppose there is a method that does some work named Save:

public void Save(Data data, string path) {

    var formatter = new BinaryFormatter();

    using(var fs = new FileStream(path, FileMode.OpenOrCreate))

        formatter.Serialize(fs, this);

}

This is a clearly a synchronous method that serializes some data object to a file. Even if I expect the data to be fairly small, I may want to create an asynchronous version instead:

public async Task SaveAsync(Data data, string path) {

    await Task.Run(() => {

        var formatter = new BinaryFormatter();

        using(var fs = new FileStream(path, FileMode.OpenOrCreate))

            formatter.Serialize(fs, this);

    });

}

To make the change to an asynchronous method, we need a couple of things:

  • 1. The method should return Task or Task<T> (depends on whether the method has some result or not).
  • 2. Mark the method as async. That’s not strictly required in our case – we can simply return the Task from Task.Run. Adding async requires the use of await somewhere within the code – in this case since this is just one operation, we await the Task completion. None we don’t return anything. The compiler does the right thing and completes the Task that’s returned.

The client now simply awaits the end of the operation:

public async void SaveData(string path) {

    await SaveAsync(_data, path);

}

  • The point of await is letting the thread return until the awaited Task is complete. What happens to that thread? If it’s in some server application, the operation was probably initiated by some thread from the Thread Pool, so that thread can return to the pool and serve some other client, thus not wasting threads. In a UI client scenario, the thread returns to its all-important message pumping activity.
  • Suppose the SaveData method was initiated by a button click. In this case while the thread returns the user can click the button again, causing reentrancy to the SaveData method and the initiation of the save operation again. This can cause problems – in the above code the file may be already exclusively open, so a second attempt will throw an exception.
  • This means we need to think about all possible scenarios while operations are awaited. At a minimum, we would disable the button that initiated the operation until it’s done:
  • public async void SaveData(string path) {

        _button.IsEnabled = false;

        await SaveAsync(_data, path);

        _button.IsEnabled = true;

    }

This is certainly better, but we may need to concern ourselves with other operations. Suppose some other button on the UI changes the data object that is being persisted. What then? We may want to disable that button as well, and maybe other controls for the same reason. In XAML based technologies (WPF/Silverlight/Windows 8 Store) we can leverage data bindings to make this simpler and manageable. For example, suppose there is some ViewModel that manages the current view. We can set some property that indicates “busyness”:

public async void SaveData(string path) {

    _vm.IsBusy = true;

    await SaveAsync(_data, path);

    _vm.IsBusy = false;

}

And a typical control can be bound to the property like so:

IsEnabled="{Binding IsBusy, Converter={StaticResource notConverter}}"

This assumes the converter negates the property’s value (we can setup a IsNotBusy property to remove the need for a converter).

[Of course, we can use a Command for the button, and make it available only when non-busy]

Sometimes, we may want to invoke an asynchronous method synchronously. Since we get back a Task, we can simply call Wait on that:

SaveAsync(_data, path).Wait();

This blocks the thread, making the call effectively synchronous. If the operation returns a result, the code would change to:

var result = SaveAsync(_data, path).Result;

Reading the Task<T>.Result property cause a call to Wait() before the result can be retrieved.

 

Conclusion

async/await is a great tool for not letting threads sleep unnecessarily; we just need to remember that although the code may look synchronous, it’s actually not. This can cause reentrancy (and other similar) issues that we must deal with.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*