Implementing an asynchronous settings service, Part 3 : Getting notified
and getting notified about changes in these settings.It can be particularly interesting, for a ViewModel in a weather app to be notified that a new temperature unit has been selected, or that it needs to be notified that the user is now signed in and the UI should change accordingly.Surely, interacting with the Settings Service directly may not be always the best idea, and that there should be some abstraction between a view model and the settings service (particularly for the user credentials), but you get the idea.
Notifying of Settings Changes
Now going a bit farther, code that depends on these settings may need to be notified that a setting has changed. Other than the bad solution of polling for values of a specific setting, a better one is to use C# events:
public class SettingsService : ISettingsService { // ... public event EventHandler<SettingChangedEventArgs> SettingChanged; // ... public async Task Write<T>(string key, T value) { await Task.Run( () => { _data.Values[key] = value; if (SettingChanged != null) { SettingChanged(this, new SettingChangedEventArgs(key, value)); } } ); } }
This is the commonly used way to notify listeners in C#. It requires the creation of a SettingChangedEventArgs to notify the listeners of the event.
You'll note, if you've read the Part 2 of my Async Tips and Tricks, that there's a call to Task.Run, which forces the inner code to run in the background. It is the inner code which raises the event, which means the subscribers will be notified in the background thread's context. This may require the listener to post back to the UI thread to perform work.
Using this event is fairly easy WPF-like frameworks, let’s look at a ViewModel :
public class MainViewModel : IDisposable, INotifyPropertyChanged { // ... public MainViewModel(ISettingsService settings) { // ... _settings.SettingChanged += OnSettingChanged; } public void Disposable() { _settings.SettingChanged -= OnSettingChanged; } private void OnSettingChanged(object sender, Settings.SettingChangedEventArgs e) { switch (e.Key) { case "MySetting": MySetting = (string)e.Value; break; } } // ... }
You’ll note that the property filtering logic must be integrated directly into the OnSettingsChanged method, but also that the notion of setting type has been lost because the event cannot be typed. It is also required for the class to now implement deterministic cleanup with the IDisposable interface to avoid memory leaks that may be introduced by the use of the C# event.
Finally, the same logical operation needs to be split at least in three places, which is not very convenient for maintenance and code readability.
Next…
This simple example is taking a synchronous approach, and using the standard C# techniques, is make use of the three different elements: asynchrony, parallelism and the time that passes by in the form of notifications. C# and the .NET BCL do not provide out of the box a way to blend these three concepts, which requires the introduction of plumbing code make them work properly together.
In the next article, I’ll talk about how to implement this logic using the Reactive Extensions and a bit of the Rxx library, and reduce (a lot) the code needed to perform the same operations or at the very least, group them in the same logical place to make them more readable.