MichaelLPerry's blog

Multi-threading

Multithreaded programming doesn't have to be hard. You just need to learn a few patterns.

Here's a common problem. How do you notify the UI thread that something has changed on a background thread? The INotifyPropertyChanged contract requires that the PropertyChanged event be fired on the UI thread. There are ways of marshalling that event from the background to the foreground, but wouldn't it be nice if you didn't have to?

Update Controls takes care of this problem for you. You make sure your code is thread-safe, and Update Controls will make sure that dependents are updated, no matter what thread they are on.

A thread-safe model
For Update Controls to work properly -- and indeed for your entire application to work properly -- you need your data model to be thread safe. That means that anything that can change is protected with a lock. If it can change (and you are using Update Controls), then it should have an Independent sentry. Just be sure to call OnGet and OnSet inside the lock.

public class Playlist
{
    private int _id;
    private string _name;

    public Playlist(int id)
    {
        _id = id;
    }

    public int ID
    {
        get { return _id; }
    }

    #region Independent properties
    // Generated by Update Controls --------------------------------
    private Independent _indName = new Independent();

    public string Name
    {
        get
        {
            lock (this)
            {
                _indName.OnGet();
                return _name;
            }
        }
        set
        {
            lock (this)
            {
                _indName.OnSet();
                _name = value;
            }
        }
    }
    // End generated code --------------------------------
    #endregion
}


When dealing with collections, we need to be a little careful. If you return an IEnumerable that traverses a collection, that IEnumerable will leave the lock. This will cause problems, as another thread can come in and modify the collection while you are traversing it. To be safe, we need to create a copy of the collection within the lock.

public class MusicLibrary
{
    private List<Playlist> _playlists = new List<Playlist>();

    #region Independent properties
    // Generated by Update Controls --------------------------------
    private Independent _indPlaylists = new Independent();

    public Playlist GetPlaylist(int playlistID)
    {
        lock (this)
        {
            // This method changes the list.
            _indPlaylists.OnSet();

            // See if we already have this one.
            Playlist playlist = _playlists.Where(p => p.ID == playlistID).FirstOrDefault();
            if (playlist != null)
                return playlist;

            // If not, create it.
            playlist = new Playlist(playlistID);
            _playlists.Add(playlist);
            return playlist;
        }
    }

    public void DeletePlaylist(Playlist playlist)
    {
        lock (this)
        {
            _indPlaylists.OnSet();
            _playlists.Remove(playlist);
        }
    }

    public IEnumerable<Playlist> Playlists
    {
        get
        {
            lock (this)
            {
                _indPlaylists.OnGet();
                // Return a copy of the list so that it can be accessed in a
                // thread-safe manner.
                return new List<Playlist>(_playlists);
            }
        }
    }
    // End generated code --------------------------------
    #endregion
}

With this, your model is safe to access from multiple threads. So let's create one.

Create a thread to communicate with external systems
The UI thread is for the user. If you use it to communicate with external systems, it might hang. Then the user will have to decide whether to "Continue Waiting" or "Switch To...". Switch to what, a Mac? But seriously, it's better if you spawn a new thread to do all of your communication with external systems.

Commuter communicates with iTunes. So I created a separate thread to synchronize between iTunes and my data model. It wakes up every 10 seconds and refreshes the playlists.

public class ITunesSynchronizationService
{
    private MusicLibrary _musicLibrary;
    private Thread _thread;
    private ManualResetEvent _stop;
    private bool _first = true;

    private string _lastError = string.Empty;
    private Independent _indLastError = new Independent();

    public ITunesSynchronizationService(MusicLibrary musicLibrary)
    {
        _musicLibrary = musicLibrary;
        _thread = new Thread(ThreadProc);
        _thread.Name = "iTunes synchronization service";
        _stop = new ManualResetEvent(false);
    }

    public void Start()
    {
        _thread.Start();
    }

    public void Stop()
    {
        _stop.Set();

        // Give it 30 seconds to shut down.
        _thread.Join(30000);
    }

    public string LastError
    {
        get
        {
            lock (this)
            {
                _indLastError.OnGet();
                return _lastError;
            }
        }

        private set
        {
            lock (this)
            {
                _indLastError.OnSet();
                _lastError = value;
            }
        }
    }

    private void ThreadProc()
    {
        while (ShouldContinue())
        {
            try
            {
                // Connect to iTunes.
                iTunesApp itApp = new iTunesAppClass();

                // List all of the playlists.
                List<Playlist> oldPlaylists = _musicLibrary.Playlists.ToList();
                foreach (IITUserPlaylist itPlaylist in itApp.LibrarySource.Playlists.OfType<IITUserPlaylist>())
                {
                    string name = itPlaylist.Name;
                    Playlist playlist = _musicLibrary.GetPlaylist(itPlaylist.playlistID);
                    if (name != null && playlist.Name != name)
                        playlist.Name = name;
                    oldPlaylists.Remove(playlist);
                }

                // Delete all that are no longer in iTunes.
                foreach (Playlist playlist in oldPlaylists)
                    _musicLibrary.DeletePlaylist(playlist);

                LastError = string.Empty;
            }
            catch (Exception ex)
            {
                LastError = ex.Message;
            }
        }
    }
    private bool ShouldContinue()
    {
        // Don't wait the first time.
        if (_first)
        {
            _first = false;
            return true;
        }

        // Wake up every 10 seconds, or when the thread should stop.
        return !_stop.WaitOne(10000);
    }
}

Notice the LastError property. This is a thread-safe way for the service to communicate its status with the user. This property is bound to a TextBlock on the UI.

The iTunes synchronization service makes changes directly to the data model. Because the data model is thread-safe, the UI thread and the background thread can share this resource. Update Controls will notify the UI thread whenever the background thread changes the data model.

Try it out
Download the code. When you run Commuter, it will launch iTunes. Even as iTunes launches, the Commuter user interface is responsive. If you are fast enough, you can open the Playlist drop-down before it is populated. But even if COM is faster than the mouse, you can try a few experiments.

Select a playlist in Commuter. Then switch to iTunes and rename that playlist. Within 10 seconds you'll see the selected playlist's name change.

Bring iTunes to the front, but leave Commuter visible underneath it. Place your mouse on the drop-down. Press Ctrl+N to create a new playlist, then click the mouse to bring Commuter to the front and open the drop-down. Within 10 seconds, "untitled playlist" will appear in the list.

Multi-threaded programming still requires some care, but Update Controls can at least take care of updating the UI when the background thread changes the data model.

View Model and Navigation Model

Update Controls automatically discovers dependencies upon your data model, and updates the UI when that data changes. It does not require that you register for or fire PropertyChanged events.

Step 1: Write your data model
Your data model is a set of plain-old CLR objects (POCOs). These objects have fields that store data, and properties that expose that data to consumers. They may also have validation logic, business logic, and other methods. The data model will be persisted, probably via a web service.

public class Customer
{
    private string _name;
    private List<Invoice> _invoices = new List<Invoice>();

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public IEnumerable<Invoice> Invoices
    {
        get { return _invoices; }
    }

    public Invoice NewInvoice(string number)
    {
        Invoice invoice = new Invoice(number);
        _invoices.Add(invoice);
        return invoice;
    }
}

public class Payment
{
    private Customer _customer;
    private List<Invoice> _paidInvoices = new List<Invoice>();

    public Payment(Customer customer)
    {
        _customer = customer;
    }

    public Customer Customer
    {
        get { return _customer; }
    }

    public IEnumerable<Invoice> PaidInvoices
    {
        get { return _paidInvoices; }
    }

    public void AddPaidInvoice(Invoice invoice)
    {
        _paidInvoices.Add(invoice);
    }

    public void RemovePaidInvoice(Invoice invoice)
    {
        _paidInvoices.Remove(invoice);
    }
}


Step 2: Add Independent sentries
Add a reference to UpdateControls.Light.dll. Then create an Independent sentry for each field in your data model that can change. Call OnGet whenever you read the field, and OnSet whenever you change it.

public class Customer
{
    private string _name;
    private List<Invoice> _invoices = new List<Invoice>();

    private Independent _indName = new Independent();
    private Independent _indInvoices = new Independent();

    public string Name
    {
        get { _indName.OnGet(); return _name; }
        set { _indName.OnSet(); _name = value; }
    }

    public IEnumerable<Invoice> Invoices
    {
        get { _indInvoices.OnGet(); return _invoices; }
    }

    public Invoice NewInvoice(string number)
    {
        _indInvoices.OnSet();
        Invoice invoice = new Invoice(number);
        _invoices.Add(invoice);
        return invoice;
    }
}

public class Payment
{
    private Customer _customer;
    private List<Invoice> _paidInvoices = new List<Invoice>();

    private Independent _indPaidInvoices = new Independent();

    public Payment(Customer customer)
    {
        _customer = customer;
    }

    public Customer Customer
    {
        get { return _customer; }
    }

    public IEnumerable<Invoice> PaidInvoices
    {
        get { _indPaidInvoices.OnGet(); return _paidInvoices; }
    }

    public void AddPaidInvoice(Invoice invoice)
    {
        _indPaidInvoices.OnSet();
        _paidInvoices.Add(invoice);
    }

    public void RemovePaidInvoice(Invoice invoice)
    {
        _indPaidInvoices.OnSet();
        _paidInvoices.Remove(invoice);
    }
}


Step 3: Write your navigation model
The navigation model keeps track of all of the user's selection. It helps the user navigate through the data model. Use Independent sentries, just like you did for the data model. But unlike the data model, the navigation model is not persistent.

public class PaymentNavigationModel
{
    private Invoice _selectedUnpaidInvoice;
    private Invoice _selectedPaidInvoice;

    private Independent _indSelectedUnpaidInvoice = new Independent();
    private Independent _indSelectedPaidInvoice = new Independent();

    public Invoice SelectedUnpaidInvoice
    {
        get { _indSelectedUnpaidInvoice.OnGet(); return _selectedUnpaidInvoice; }
        set { _indSelectedUnpaidInvoice.OnSet(); _selectedUnpaidInvoice = value; }
    }

    public Invoice SelectedPaidInvoice
    {
        get { _indSelectedPaidInvoice.OnGet(); return _selectedPaidInvoice; }
        set { _indSelectedPaidInvoice.OnSet(); _selectedPaidInvoice = value; }
    }
}


Step 4: Write your view model
The view model interprets the data model for the view. It has no data of its own. It just references the data model and the navigation model. It has a property for every bindable control in the view. It also has an ICommand property for every action the user can perform.

public class PaymentViewModel
{
    private Payment _payment;
    private PaymentNavigationModel _navigation;

    public PaymentViewModel(Payment payment, PaymentNavigationModel navigation)
    {
        _payment = payment;
        _navigation = navigation;
    }

    public string CustomerName
    {
        get { return _payment.Customer.Name; }
        set { _payment.Customer.Name = value; }
    }

    public IEnumerable<Invoice> PaidInvoices
    {
        get { return _payment.PaidInvoices; }
    }

    public IEnumerable<Invoice> UnpaidInvoices
    {
        get { return _payment.Customer.Invoices.Except(_payment.PaidInvoices); }
    }

    public Invoice SelectedPaidInvoice
    {
        get { return _navigation.SelectedPaidInvoice; }
        set { _navigation.SelectedPaidInvoice = value; }
    }

    public Invoice SelectedUnpaidInvoice
    {
        get { return _navigation.SelectedUnpaidInvoice; }
        set { _navigation.SelectedUnpaidInvoice = value; }
    }

    public ICommand AddPaidInvoices
    {
        get
        {
            return MakeCommand
                .When(() => _navigation.SelectedUnpaidInvoice != null)
                .Do(() =>
                {
                    _payment.AddPaidInvoice(_navigation.SelectedUnpaidInvoice);
                    _navigation.SelectedPaidInvoice = _navigation.SelectedUnpaidInvoice;
                    _navigation.SelectedUnpaidInvoice = null;
                });
        }
    }

    public ICommand RemovePaidInvoices
    {
        get
        {
            return MakeCommand
                .When(() => _navigation.SelectedPaidInvoice != null)
                .Do(() =>
                {
                    _payment.RemovePaidInvoice(_navigation.SelectedPaidInvoice);
                    _navigation.SelectedUnpaidInvoice = _navigation.SelectedPaidInvoice;
                    _navigation.SelectedPaidInvoice = null;
                });
        }
    }
}


Step 5: Set the data context
Now comes the magic. At no point did you fire or register for PropertyChanged events. You just wrote code that was concerned with the rules of your business (data model) and your user interface (view model and navigation model). Update Controls will fire those PropertyChanged events for you, if you wrap your view model before giving it to the view:

Customer customer = new Customer();
// Load the customer from the web service.

Payment payment = new Payment(customer);
// Load the payment from the web service.

DataContext = ForView.Wrap(
    new PaymentViewModel(
        payment,
        new PaymentNavigationModel()));


Update Controls makes it much easier to create line-of-business applications in Silverlight 3, because you don't have to deal with INotifyPropertyChanged.

Linq to SQL

Download the source code and follow along.

The biggest challenge implementing the MVVM pattern is the View Model responding to changes in the Data Model. Usually, the View Model needs to subscribe to the PropertyChanged events fired by the Data Model, update its internal state, and then fire its own PropertyChanged events for the View. That's the hard way.

The easy way is to use Update Controls. With Update Controls, you just decorate the Data Model and wrap the View Model. The library takes care of everything in between.

Decorate a Linq to SQL data model
You decorate the data model by adding Independent sentries to every property. Whenever the property is accessed, call OnGet(). Whenever it is modified, call OnSet(). To decorate a Linq to SQL data model, we just need to inject these calls into the generated code. Unfortunately, the Linq to SQL code generator does not give you a way to easily tweak its output.

Damien Guard has solved that problem. His open source project LINQ to SQL templates for T4 is incredibly easy to set up and start using. It drops straight into Visual Studio and replaces the build-in code generator with one that you control. With just a few edits to his provided T4 template, I replaced INotifyPropertyChanged with Independent sentries.

I added a sentry for each single-valued property, and called OnGet() inside of the getter and OnSet() in the setter. I also added a sentry for each collection. They require OnGet() in the getter, and OnSet() when something is added or removed. Finally, I added a sentry per table, to take care of the top-level queries. For these, I call OnSet() on any insert, update, and delete. The final T4 template is in the example source code.

Wrap the view model for WPF
Wrapping the view model for the view is a one-liner.

public Window1()
{
    InitializeComponent();

    // Create a data model and a navigation model.
    _blog = new Blog();
    BlogNavigationModel navigationModel = new BlogNavigationModel();

    // Put them both in a view model, then wrap it for the view.
    this.DataContext = ForView.Wrap(new BlogViewModel(_blog, navigationModel));
}

We create a view model based on the decorated data model and a similarly decorated navigation model (more on that later). Then we let Update Controls wrap it up before we give it to the view.

View model per scope
NoteworthyThe example application -- Noteworthy -- is a blog editor. Different parts of the view focus on different granularities of data. The main view (shown in red) focuses on the blog as a whole. The article list items and detail pane (shown in blue) focus on a single article. And the tag list items (shown in green) focus on a single tag.

We define one view model class for each of these three scopes. Each one takes constructor parameters to put itself in context. So the BlogViewModel takes a Blog, the ArticleViewModel takes a Blog and an Article, and the TagViewModel takes a Blog and a Tag.

The DataContext property of a WPF control determines where it begins for data binding. If you don't set it, the control inherits it from its parent. If the control is an item in a list, then it is automatically set to an element of the ItemsSource. Finally, you can data bind to a property of its parent scope. Noteworthy uses all three techniques.

Property per control attribute
WPF data binding connects control attributes to object properties. So within each view model, we define a property for each attribute of a control. The view model exists to serve of the view, so this one-to-one mapping is to be expected. The view model is an isolation layer designed to keep these view-specific concerns out of your data model.

Navigation model per user context
The user of your application has a conceptual model of how controls should work together. Noteworthy is a single window program, so they expect all of the controls on that window to interact appropriately. If it had multiple child windows all within a parent, they would expect each window to be its own context, but participate within the context of the main window. And if it were a composite application, they would expect all of the components to work together.

Where the user draws their conceptual boundary, we create a navigation model. A navigation model records the user's point-of-view as they navigate through the application. It keeps track of their selection and temporary input.

All of the state in the navigation model is transient. Nothing is written to the database. Persistent state belongs in the data model. This keeps the view model completely stateless. The view model has only behavior, which depends upon the data model and the navigation model.

Because the navigation model is transient, we just write fields, select them, and hit Ctrl+D, G to generate the properties. For example, here's the property that records the selected article. The part that I wrote by hand is highlighted:

public class BlogNavigationModel
{
    private Article _selectedArticle;

    #region Independent properties
    // Generated by Update Controls --------------------------------
    private Independent _indSelectedArticle = new Independent();

    /// <summary>
    /// The article for which to display details.
    /// </summary>
    public Article SelectedArticle
    {
        get { _indSelectedArticle.OnGet(); return _selectedArticle; }
        set { _indSelectedArticle.OnSet(); _selectedArticle = value; }
    }
    // End generated code --------------------------------
    #endregion
}

The properties of the navigation model are always data model types, never view model types. The view model depends upon the navigation model, not vice-versa.

Explore on your own
That's just enough to get you started. There are many interesting patterns that came together in the making of Noteworthy. Here are some that you might want to examine on your own:

  • The BlogViewModel.Articles property is filtered by the selected tag.
  • The article detail pane is a Grid within a Grid. The outer grid binds IsEnabled, while the inner grid binds DataContext.
  • The TagListBoxStyle resource binds the ListBoxItem.IsSelected attribute to the TagViewModel.TagIsSelected property to support multiple selection.
  • The Blog data model class uses a Dependent sentry to cache Tags.
  • Every view model setter and command that affects the data model calls SubmitChanges.
  • The ArticleViewModel.AvailableTags property uses .Except() to get all tags not assigned to the article.
  • The BlogViewModel.SelectedArticle property wraps the data object from the navigation model in a view model, and then unwraps it on the way back.
  • The first tag in BlogViewModel.Filters is actually not a tag at all. It is a null to represent the lack of a filter.

Expect future posts to return to this example and explore these points in more detail.

Syndicate content