Implementing an asynchronous settings service, Part 2 : Writing a setting

By jay at October 08, 2012 20:12 Tags: , , , , ,

tl;dr: This article is the second of a series talking about the implementation of an async user Settings Service, using the C# async keyword. This part talks about writing new settings values, asynchronously and the challenges associated with it.

 

In this continuing implementation of a Settings Service series, I'll continue with the addition new features to the service.

Part one talked about reading the settings asynchronously, and this time, we'll talk about writing user settings new values.

 

Writing to the Settings Service

Now let's say that the user has selected a new temperature unit in a weather app, writing a new value to the settings service takes a few more lines, by adding this:

public string TemperatureUnit
{
    // ...
    set
    {
        _temperatureUnit = value;

        StoreValue("TemperatureUnit", _temperatureUnit);

        OnPropertyChanged();
    }
}

private async void StoreValue(string key, string value)
{
    await _settings.Write(key, value);
}

This is a pretty simple code, and you'll notice that it uses async void, which should be avoided most of the time. But in this case, since the property is synchronous, using an async Task is not very useful, and produces a compiler warning.

The warning removal is actually not an excuse, but the environment leads us to write that kind of code, because of the mix of synchronous and asynchronous code.

 

Cancellation Management

Again here, cancellation needs to be added using an explicit cancellation token, passed-in as a parameter which is not represented in the previous example.

But for the sake of clarity, let's look at this :

public class MainViewModel : IDisposable, INotifyPropertyChanged
{
    // ...
    private CancellationTokenSource _source = new CancellationTokenSource();

    public void Dispose()
    {
        // ...
        _source.Dispose();
    }

    private async Task StoreValue(string key, string value)
    {
        await _settings.Write(key, value, _source.Token);
    }
}

This implementation is using the cancellation token provided by the CancellationTokenSource that can be disposed at any time. This will work properly if the view model is disposed before the end of an async operation.

But what happens if a write operation takes way too much time to complete ? Do we keep stacking up these async calls ?

This could happen if the settings service is actually sending data off to a remote server, and the server is not responding properly. Another solution would be to create a cancellation token for each call make to Write, and cancel it if there's a new one made before the other ends.

Let's see that :

private IDisposable _outstandingWrite;

private async void StoreValue(string key, string value)
{
    if (_outstandingWrite != null)
    {
        _outstandingWrite.Dispose();
    }

    var source = new CancellationTokenSource();
    _outstandingWrite = source;

    await _settings.Write(key, value, source.Token);

    _outstandingWrite = null;
}

By creating a disposable field that gets disposed when entering the StoreValue, it's now possible to cancel an outstanding Write operation.

Unfortunately, this code only guaranteed to work if it is running on the UI Thread, because there's only one UI Thread and that processing is serialized. By default, if nothing in particular is done since the property will mostly be assigned by the data-binding engine, everything will run on the Dispatcher.

But if for some reason, the code ends up running on a different thread, the problem here is that the _outstandingWrite private field is now a parallelism liability that could be modified by multiple threads at the same time. We can't put a lock here to protect it though, because it not permitted in an async method.

This leaves us using the Interlocked class :

private IDisposable _outstandingWrite;

private async Task StoreValue(string key, string value)
{
    var source = new CancellationTokenSource();

    // Get the currently running operation, if any
    var existingWrite = Interlocked.Exchange(ref _outstandingWrite, source);

    if (existingWrite != null)
    {
        existingWrite.Dispose();
    }

    await _settings.Write(key, value, source.Token);

    // The operation is done, reset the oustanding write
    Interlocked.Exchange(ref _outstandingWrite, null);
}

That way, we've synchronized the access to the _outstandingWrite, without the use of a lock statement.

You'll probably agree with me though, that this is getting a bit complex.

 

Write Throttling

Some more complexity comes when, let’s say, you want to throttle the updates coming in from the settings property. This can happen when it's been data-bound using a Two-Way mode binding on a TextBox.

The code will actually send the new values to the settings service when a certain amount of time has passed, to avoid overloading the service underneath. Let’s take a look:

int storeCounter = 0;

private async void StoreValue(string key, string value)
{
    var storeCounterCapture = ++storeCounter;

    await Task.Delay(TimeSpan.FromMilliseconds(300));

    if (storeCounterCapture == storeCounter)
    {
        await _settings.Write(key, value);
    }
}

The throttle logic is integrated directly into the ViewModel, which is a lot of plumbing to repeat if there are multiple settings. Of course, this can be refactored and placed into a utility class, but there’s still going to be a need for state to be explicitly maintained to perform the throttle.

I've excluded the cancellation logic from this sample, but it'd eventually need to be added here.

This can also be used to persist the data to the storage, using the IsolatedStorageSettings.Save() method, but only write when enough time has passed by.

 

Next...

Writing to the settings service is actually not as easy as it seems, as there can be multiple concurrency scenarios to deal with.

In the next article, we'll talk about notifying code that the settings have changed which could be used to alter the behavior of the application when, let's say, the temperature unit has changed.

blog comments powered by Disqus

About me

My name is Jerome Laban, I am a Software Architect, C# MVP and .NET enthustiast from Montréal, QC. You will find my blog on this site, where I'm adding my thoughts on current events, or the things I'm working on, such as the Remote Control for Windows Phone.