Implementing an asynchronous settings service, Part 1 : Going Async
TL;DR: This article is part of a series about implementing asynchronous services contract, and starts by an the creation of basic functionality for a User Settings storage service using C# 5.0 async features. In the next episode, we'll talk about writing a user setting and consuming settings change notifications.
Most applications require the storage of user settings, such as the login, authentication token, preferences, you name it. All these settings have been stored in many locations over the years, such ini files, AppSettings, IsolatedStorageSettings in Silverlight and more recently in RoamingStorage or LocalStorage in WinRT, etc…
Most of the time, this is pretty much CRUD-like contracts that do not offer much to perform asynchronous reading/writing, easy two-way data-binding and notifications. Some offer change notifications, but most of the time, this is related to roaming settings that have been updated.
Reading settings in a synchronous way is a common performance issue, and most of the time accessing the data can be expensive, which is not good for the user experience. For instance, the Silverlight and Windows Phone IsolatedStorageSettings implementation has a pretty big performance hit during the first access, due to the internal use of Xml Serialization (and its heavy use of reflection). It also requires to be synchronized during persistence, and its persistence takes a lot of time too, suspending simultaneous read or write operations.
In this article, I’m going to discuss an implementation of a service that abstracts the use of application or users settings using the C# async/await keywords.
[more]
A basic settings service
Considering that reading settings needs to be asynchronous, we can implement an interface that looks like this:
public interface ISettingsService { Task<T> Read<T>(string key, Func<T> defaultValue); Task Write<T>(string key, T value); }
This is very simple service that can read and write content to a Settings storage.
public class SettingsService : ISettingsService { private ApplicationDataContainer _data; private object _gate = new object(); public SettingsService(ApplicationDataContainer data) { _data = data; } public async Task<T> Read<T>(string key, Func<T> defaultValue) { return await Task.Run( () => { lock(_gate) { object value; if (!_data.Values.TryGetValue(key, out value)) { value = defaultValue(); } } return (T)value; } ); } public async Task Write<T>(string key, T value) { await Task.Run( () => { lock(_gate) { _data.Values[key] = value; } } ); } }
This basic implementation turns the synchronous API of ApplicationDataContainer into an asynchronous API. Concurrent access is protected via a simple monitor (lock), and this is needed because of the use of Task.Run which runs in the background, using the Task Pool.
Consuming the Settings Service
Now, because of the async nature of this service, this means that the code that consumes it needs to be adapted.
Here is an example :
public class MainViewModel : INotifyPropertyChanged { private readonly ISettingsService _settings; public MainViewModel(ISettingsService settings) { _settings = settings; FillSettings(); } private async void FillSettings() { MySetting = await _settings.Read<string>("MySetting", () => "42"); } private string _mySetting = null; public string MySetting { get { return _mySetting; } set { _mySetting = value; OnPropertyChanged(); } } private void OnPropertyChanged([CallerMemberName] string propertyName = null) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public event PropertyChangedEventHandler PropertyChanged; }
Note that the FillSettings async void method is called from the MainViewModel to initiate the get the value of a setting. Constructors cannot be marked async.
Property State Management
There a first problem at this point. While the async call is in progress, the value of the MySetting property will be null. This means that code that may depend on the availability of this value will most probably break and needs to be refactored to wait for that value to be available.
In this example, there is actually only one asynchronous assigned property, but if the view model code depends on multiple properties such as code in a UI command, state management code can grow pretty fast to handle all the cases. Going a bit further, the code that waits for the initialization of all properties will not necessarily be the same that handles the change (and error conditions) of these properties after they've been assigned their first value, adding another layer of complexity.
Task Cancellation
There is a second problem with the FillSettings method. It does not provide any cancellation mechanism, meaning that the async processing will continue even though the view model has been destroyed. The method can be changed to an async task, and could be passed a Cancellation token, but this increases the plumbing of the method. In this particular case though, since the underlying Settings is synchronous, cancelling it is not possible. However, in the case of an actual async operation, such as using a WebRequest, cancelling is possible.
The cancellation concept in the TPL is something that permeates at all levels of an API and underlying code. It is required for every method used in the underlying code to expose a CancellationToken, making cancellation a very apparent and viral consideration. It pretty much needs to be passed as a parameter or as part of a closure, everywhere in the inner call stack of an async method.
UI Thread Affinity
The third problem with this implementation is that the FillSettings method is implicitly bound to the UI Thread, because of the fact that the PropertyChanged event must be raised on the UI Thread. Moving the creation of this view model to a background thread would require a change in the way the property changed is raised, such as introducing explicitly the notion of UI Thread.
In all fairness, many MVVM frameworks take care of all this UI thread plumbing back and forth plumbing, but some don’t.
A solution for this would be to do the following :
public class MainViewModel : IDisposable, INotifyPropertyChanged { private SynchronizationContext _dispatcher; public MainViewModel(ISettingsService settings, SynchronizationContext dispatcher) { // ... _dispatcher = dispatcher; } // ... private void OnPropertyChanged([CallerMemberName] string propertyName = null) { if (PropertyChanged != null) { _dispatcher.Post(_ => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)), null ); } } }
Injecting a synchronization context allows for an easy dispatch to the UI thread, but also allows for an easier unit testing and avoid a dependency to the actual dispatcher.
In the next episode...
We'll talk about writing a new setting value3 to the settings service, and how to get notified that a setting has changed.