Archives

Anticipation

  • No dates present

Multithreaded Collections and WPF

WARNING: As discussed in the comments, this collection class uses weak events, and (due to a bug in the .NET framework itself) weak events are not reliable unless you’re using .NET 3.5 SP1 or higher. If you’re stuck with an earlier version of the framework (or don’t want to verify that end users have installed the service pack), you could try using Kevin Kerr’s class, which works around the problem by treating the collection lifetimes differently than mine. (My one keeps the domain collection entirely separate and assumes it can out-live the UI; his ties them together and assumes that the collection is created by the UI in the first place; thus his one doesn’t need the weak events.)

It’s about time now for another programming post. (I noticed a little while ago that I appear to be maintaining a “two posts other, one post programming” pattern. This was unintentional at first, but once I noticed I figured I might as well keep it up. We’ll see how long it lasts before I get bored.) And this one’s going to be a long one.

I’ve been experimenting a bit in recent months with WPF, and in particular trying to do things the “WPF way” by using data binding to tie domain objects to the UI rather than writing code. This all works fairly well once you get the basic concepts down, but there’s one gaping hole in the framework: while it can cope with property changes occurring across threads, it can’t cope with collection changes across threads. This means that if you’re binding to a collection (to display a list of items somewhere), then that collection can only be modified on the UI thread, not any other thread. If you break this rule, then things may seem to work for a while, but you’ll eventually either get a simple exception (a NotSupportedException saying “This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.”) or a weirder exception (such as an IndexOutOfRangeException from deep within the internals of the framework).

But sometimes (eg. for performance reasons), you really do need to update that collection from a different thread. You might think you can get away with just making the collection synchronised (wrapping every operation in a lock), but there are two problems with that. First, you would have to synchronise enumeration operations too, which you can’t do from within the collection class — and since the WPF code is written assuming a single thread, it’s unlikely it will be doing the synchronisation itself. Second, from some of the weird messages it produces, it’s clear that after getting a “collection changed” notification the framework will go and access the collection to retrieve additional info — and by the time it does that the collection may have changed again, meaning it will be accessing the wrong items and getting itself horribly confused.

Several other people have posted blog entries on this topic, but most seem to have missed that last point. I don’t want to bore you with the details, but that means that there is simply no safe way to modify the collection from any thread other than the UI thread. This point was also made by Bea Costa, one of the Microsoft WPF gurus. Her post does contain a workaround for this, but it operates by delaying the operation of the worker thread until the UI has a chance to update the collection. For the program I was working on (where the worker thread needed to be performant), this wasn’t acceptable.

So, we have to somehow be able to update the collection from any thread while simultaneously not updating the collection from any thread but the UI thread. Sounds impossible? Well, it is. So let’s change the rules.

Instead of using one collection, we’ll use two, linked together. The idea is that one collection is the “master” and can be updated from any thread, and any changes to that collection then get propagated over to the second collection, which is only actually updated on the UI thread. It’s this second collection that the UI binds to. The drawback of this approach is that the data the UI shows might be “stale” (ie. it could render an item that’s since been removed from the master collection), but this is self-correcting, and it will get itself up to date in short order.

There are some caveats with the implementation I’m going to outline here:

  • The UI only gets a read-only collection, for simplicity. The assumption is that it’s only worker threads that are adding/removing from the collection, since if collection modification were UI-driven then the collection might as well only be present in the UI thread to begin with. (Note that the UI thread can still update the collection, if it needs to — it just needs to update the “master”, not the UI-bound collection.)
  • Only one “level” is supported. If your collection contains objects that themselves contain collections, nothing fancy is done about this and you’ll experience the same threading problems if you try to use them.
  • Objects within the second collection are also the “real” objects. This means that for reference-based objects, any changes to the objects will be instantly visible to all threads (and you’ll need to be careful to do your own synchronisation). Presumably the objects implement INotifyPropertyChanged to provide notification to the UI of any changes — since the UI thread does support cross-thread property change notifications, this will work fine.
  • After removing an object from a collection, it will still have a “ghost” copy in the UI-bound collection until the UI thread gets around to updating the collection. During this time, the UI thread will still receive property change notifications and may still access properties of the object. This means that you need to keep it in a valid state (eg. don’t Dispose it) for a brief period after removing it from the collection.
  • The “master” collection itself is out of scope here. If you are going to be updating it from multiple threads, you need to ensure that you’re doing your own synchronisation. If you’re not really sure where to start with that, then using this SynchronizedObservableCollection is probably the best bet. (Note that the BindableCollection shown on the same page has the serious problems I mentioned earlier. Don’t use it.)
  • Updates to the collection are presumed to not outpace the UI thread’s ability to keep up. If you completely spam the collection then it’s possible for the UI thread to get trapped trying to catch up. (There’s not a lot of processing involved, so it would take determined effort for this to be a problem.)
  • Finally, while I have tried to cover every possibility and make it as robust as I can, and while I’m using it at the moment and it seems to work fine, there is still a chance that there’s a bug or design flaw. I’m not going to warrant that this is perfect.

Anyway, I chose to attack this as two separate problems: first, make a “mirrored collection” that duplicates any changes it sees in another collection; second, make a “bindable collection” that does this via the UI thread. So without further ado, let’s get onto the code (broken up a bit to show some explanations along the way — and yes, it’s quite, quite long):

public abstract class Observable : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;
  protected virtual void OnPropertyChanged(
        PropertyChangedEventArgs e)
  {
    if (PropertyChanged != null)
    {
      PropertyChanged(this, e);
    }
  }
  protected void OnPropertyChanged(string propertyName)
  {
    OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
  }
  protected void OnPropertyChanged(params string[] propertyNames)
  {
    foreach (string propertyName in propertyNames)
    {
      OnPropertyChanged(propertyName);
    }
  }
}

Wait, that’s not a list class πŸ˜• I thought I’d begin by introducing a little support class I usually include, since I find myself writing a lot of observable objects in the WPF world. (I think they’re tidier than sprinkling dependency properties everywhere.)

You can only use it for classes that don’t need another base class, of course, but you can still get quite a lot of mileage out of it even so. And it beats having to repeat this code everywhere.

But now let’s really get on to the main list class:

public class MirroredList<T> : Observable, IList<T>, IList,
      INotifyCollectionChanged, IWeakEventListener
{
  private IList<T> _BaseList;
  private List<T> _MirrorList;
  private Queue<NotifyCollectionChangedEventArgs> _Changes
        = new Queue<NotifyCollectionChangedEventArgs>();
  private object _ChangesLock = new object();
  private object _MirrorLock = new object();
 
  public MirroredList(IList<T> baseList)
  {
    if (baseList == null)
    {
      throw new ArgumentNullException("baseList");
    }
 
    _BaseList = baseList;
    ICollection collection = _BaseList as ICollection;
    INotifyCollectionChanged changeable =
        _BaseList as INotifyCollectionChanged;
    if (changeable == null)
    {
      throw new ArgumentException("List must support "
        + "INotifyCollectionChanged", "baseList");
    }
 
    if (collection != null)
    {
      Monitor.Enter(collection.SyncRoot);
    }
    try
    {
      _MirrorList = new List<T>(_BaseList);
      CollectionChangedEventManager.AddListener(changeable, this);
    }
    finally
    {
      if (collection != null)
      {
        Monitor.Exit(collection.SyncRoot);
      }
    }
  }

So, here’s the start. You can see here that the MirroredList implements both IList<T> and IList, which in turn means that it automatically picks up ICollection<T>, IEnumerable<T>, and their non-generic counterparts. I chose to make it a List rather than a simple Collection because Lists are more functional, they provide better performance when hooked up to WPF, and even the basic ObservableCollection is actually a List, despite the name.

Since this class is the one that the UI will eventually be bound to, it naturally also has to implement INotifyCollectionChanged and INotifyPropertyChanged (the latter of which it gets through Observable). Our last interface (IWeakEventListener) will be discussed a little later on.

Here you can see that to construct a MirroredList, you need to give it the collection that it will be mirroring. This must implement IList<T> (by compile-time contract), and must also be observable (implement INotifyCollectionChanged), since that’s the mechanism we’re using to perform the mirroring.

In addition, it should implement ICollection and be synchronised. This is not enforced by the MirroredList, but bear in mind that if this isn’t the case then correctness cannot be guaranteed. If the underlying collection is modified during construction of the MirroredList (or shortly after a Clear, as we’ll see later), then you’ll likely end up with an incomplete mirror or even get an InvalidOperationException from the copy operation. Note that ObservableCollection is not synchronised, so isn’t good enough — the SynchronizedObservableCollection I referred to earlier will work fine, though.

(I’m considering whether I should make it enforce this requirement or not. It’d certainly make it safer — but as long as you’re careful it’s not impossible to get it to work ok without it. This is discussed in more detail below.)

  bool IWeakEventListener.ReceiveWeakEvent(Type managerType,
        object sender, EventArgs e)
  {
    if (managerType == typeof(CollectionChangedEventManager)
        && sender == _BaseList)
    {
      RecordChange((NotifyCollectionChangedEventArgs)e);
      return true;
    }
    return false;
  }
 
  private void RecordChange(NotifyCollectionChangedEventArgs
                 changeInfo)
  {
    bool isFirstChange = false;
    lock (_ChangesLock)
    {
      isFirstChange = (_Changes.Count == 0);
      _Changes.Enqueue(changeInfo);
    }
    if (isFirstChange)
    {
      OnCollectionDirty();
    }
  }
 
  protected virtual void OnCollectionDirty()
  {
    // This is virtual so that derived classes can eg. redirect
    // this to a different thread...
    ProcessChanges();
  }

Here’s where we get called when someone makes a change to the underlying collection. You’ll notice back in the constructor we registered ourselves as a weak listener of the collection change event, rather than adding ourselves normally using +=. The MirroredList maintains a strong reference to its base list, ensuring that as long as the MirroredList exists the base list can’t be garbage collected (although it’s not especially useful unless someone still has hold of the base list, since that’s the only way they can modify the collection).

Had we used +=, then the base list would have held a similar strong reference to the MirroredList (because that’s the way events and delegates work), which would mean that as long as the base list exists the MirroredList wouldn’t be able to be garbage collected either.

Now, since the function of the MirroredList is to be a temporary copy of the base list for purposes of binding to the UI, clearly it’s less important than the base list, and will (usually) have a shorter lifespan. So we don’t want to keep dangling copies of the MirroredList hanging around just because the base list is still there. Hence why we’re using the weak events. They ensure that the base list won’t keep the MirroredList alive when it’s no longer needed.

Incidentally, under WinForms the typical solution to this problem would be to make the list IDisposable and ensure it gets disposed during the containing form’s Dispose(true) handler. Unfortunately this isn’t viable in WPF, since WPF windows/controls aren’t disposable by default, so you can’t rely on it being called at the right time. Fortunately weak events are cleaner anyway — the Dispose trick was mostly used because weak events weren’t readily available in WinForms.

The other interesting bit in this block of code is that we only call OnCollectionDirty when the change queue used to be empty. This is because multiple changes may accumulate before we get around to processing them, and since our processing function will empty the queue it means that while the queue isn’t empty then we’ve already got an impending queue process, so there’s no need to start another one. (Note that in this specific implementation, the queue is processed immediately and it can never get multiple entries — we’ll get to the delayed-processing version later.)

The net effect of this is that OnCollectionDirty is called when the first change is made, and then won’t be called again (no matter how many more changes are made) until after ProcessChanges is called. More on this later.

  protected void ProcessChanges()
  {
    bool locked = false;
    Monitor.Enter(_ChangesLock);
    try
    {
      locked = true;
      while (_Changes.Count > 0)
      {
        NotifyCollectionChangedEventArgs info =
            _Changes.Dequeue();
        Monitor.Exit(_ChangesLock);
        locked = false;
 
        // ProcessChange occurs outside the ChangesLock,
        // permitting other threads to queue things up behind us.
        // Note that this means that if your change producer is
        // running faster than your change consumer, this
        // method may never exit.  But it does avoid making the
        // producer wait for the consumer to process.
        ProcessChange(info);
 
        Monitor.Enter(_ChangesLock);
        locked = true;
      }
    }
    finally
    {
      if (locked)
      {
        Monitor.Exit(_ChangesLock);
      }
    }
  }

This method is fairly straightforward, and we could have gotten away with using a simple lock block instead of explicitly managing the calls to Monitor. However, doing it this way lets us release the lock in the middle. And as the comment indicates, that allows other threads to produce additional changes while we’re processing an earlier one, thereby causing less holdup to the collection-modifying threads (which was one of the goals, as I said earlier). This however does introduce the racing problem I mentioned earlier on — if the change-producing threads are causing changes at a rate faster than the change-consuming thread can process them, then this while loop will never exit and the change-consuming thread will get “stuck”. It shouldn’t happen except in pathological cases, but it’s something to be aware of.

  private void ProcessChange(NotifyCollectionChangedEventArgs
                               info)
  {
    lock (_MirrorLock)
    {
      bool changedCount = true;
      switch (info.Action)
      {
        case NotifyCollectionChangedAction.Add:
          if (info.OldItems != null)
          {
            throw new ArgumentException("Old items present in "
              + "Add?!", "info");
          }
          if (info.NewItems == null)
          {
            throw new ArgumentException("New items not present "
              + "in Add?!", "info");
          }
          for (int itemIndex = 0; itemIndex < info.NewItems.Count;
              ++itemIndex)
          {
            _MirrorList.Insert(info.NewStartingIndex +
              itemIndex, (T)info.NewItems[itemIndex]);
          }
          break;
        case NotifyCollectionChangedAction.Remove:
          if (info.OldItems == null)
          {
            throw new ArgumentException("Old items not present "
              + "in Remove?!", "info");
          }
          if (info.NewItems != null)
          {
            throw new ArgumentException("New items present in "
              + "Remove?!", "info");
          }
          for (int itemIndex = 0; itemIndex < info.OldItems.Count;
              ++itemIndex)
          {
            _MirrorList.RemoveAt(info.OldStartingIndex);
          }
          break;
        case NotifyCollectionChangedAction.Move:
          if (info.NewItems == null)
          {
            throw new ArgumentException("New items not present "
              + "in Move?!", "info");
          }
          if (info.NewItems.Count != 1)
          {
            throw new NotSupportedException("Move operations "
              + "only supported for one item at a time.");
          }
          _MirrorList.RemoveAt(info.OldStartingIndex);
          _MirrorList.Insert(info.NewStartingIndex,
            (T)info.NewItems[0]);
          changedCount = false;
          break;
        case NotifyCollectionChangedAction.Replace:
          if (info.OldItems == null)
          {
            throw new ArgumentException("Old items not present "
              + "in Replace?!", "info");
          }
          if (info.NewItems == null)
          {
            throw new ArgumentException("New items not present "
              + "in Replace?!", "info");
          }
          for (int itemIndex = 0; itemIndex < info.NewItems.Count;
              ++itemIndex)
          {
            _MirrorList[info.NewStartingIndex + itemIndex]
              = (T)info.NewItems[itemIndex];
          }
          changedCount = false;
          break;
        case NotifyCollectionChangedAction.Reset:
          ICollection collection = _BaseList as ICollection;
          if (collection != null)
          {
            Monitor.Enter(collection.SyncRoot);
          }
          try
          {
            lock (_ChangesLock)
            {
              _MirrorList = new List<T>(_BaseList);
              _Changes.Clear();
            }
          }
          finally
          {
            if (collection != null)
            {
              Monitor.Exit(collection.SyncRoot);
            }
          }
          break;
        default:
          throw new ArgumentException("Unrecognised collection "
            + "change operation.", "info");
      }
 
      OnCollectionChanged(info);
      OnPropertyChanged("Items[]");
      if (changedCount)
      {
        OnPropertyChanged("Count");
      }
    }
  }

This is the heart of the class. This method’s job is to take the incoming collection change notification from the base list and make the equivalent change to the mirror list. (And it does work — I have many unit tests that prove it.)

I’ve possibly gone a little overboard with the error checking — but then again I’m not checking everything I could have, so maybe I haven’t gone far enough instead :smile:

I’ve also tried to make all operations support any number of affected items (although all of the observable collection implementations I’ve seen thus far support only one item at a time). The single exception to this was the Move operation, since it’s hard to tell exactly how to interpret the parameters of a multiple-item move. So I chose to play it safe there.

Reset is the problem case. Officially that means “something significantly weird happened to the collection”, and basically means that you can’t trust anything you think you know about it and have to requery the whole thing. So that’s exactly what we’re doing here. Note that we have to try locking the base list again (which we can only do properly if it implements ICollection) so we can copy it cleanly and avoid missing any changes. So again, if you feed the MirroredList a collection that isn’t synchronised, this is where you may run into trouble.

Fortunately, a well behaved collection doesn’t raise Reset very often. While it’d be technically valid to raise it on any collection change at all, that’d lead to very poor performance. The standard collection currently only raises this if you Clear the collection. So in other words, you’ll probably be able to get away with passing in an unsynchronised collection if:

  • only one thread modifies it (or you have some other synchronisation mechanism to ensure that at least only one thread modifies it at a time)
  • you can guarantee no thread will modify it during construction of the BindingList
  • you can guarantee no thread will call Clear while the BindingList exists
  public object SyncRoot
  {
    get { return _MirrorLock; }
  }
 
  public int IndexOf(T item)
  {
    lock (_MirrorLock)
    {
      return _MirrorList.IndexOf(item);
    }
  }
 
  public T this[int index]
  {
    get
    {
      lock (_MirrorLock)
      {
        return _MirrorList[index];
      }
    }
  }
 
  public bool Contains(T item)
  {
    lock (_MirrorLock)
    {
      return _MirrorList.Contains(item);
    }
  }
 
  public void CopyTo(T[] array)
  {
    lock (_MirrorLock)
    {
      _MirrorList.CopyTo(array);
    }
  }
 
  public void CopyTo(T[] array, int arrayIndex)
  {
    lock (_MirrorLock)
    {
      _MirrorList.CopyTo(array, arrayIndex);
    }
  }
 
  public int Count
  {
    get { lock (_MirrorLock) { return _MirrorList.Count; } }
  }
 
  public IEnumerator<T> GetEnumerator()
  {
    lock (_MirrorLock)
    {
      foreach (T item in _MirrorList)
      {
        yield return item;
      }
    }
  }
 
  public event NotifyCollectionChangedEventHandler
                  CollectionChanged;
  protected virtual void OnCollectionChanged(
                  NotifyCollectionChangedEventArgs e)
  {
    if (CollectionChanged != null)
    {
      CollectionChanged(this, e);
    }
  }

And since we’re a list class after all, we need to implement all that list-reading goodness. So there it is.

Of particular interest here is that we’re taking the unusual step of locking the entire enumeration operation. Normally, since this has potential performance issues, this decision is left up to the calling code. (And it was also much harder to do properly until the advent of yield in C# 2.0.) I chose to do it this way because again, WPF code accessing this collection is probably assuming no locks are needed since it only supports single-threaded collections. And on the off-chance it does take a lock itself, the locks are re-entrant so this won’t cause any problems.

In actual fact we probably could have gotten away with leaving the internal _MirrorList entirely unlocked, since in its final form it will only ever be accessed by the UI thread, both to read and update. But since I was trying to make the MirrorList class more flexible than that (and because I’m habitually paranoid), I chose to put the locking in anyway.

  private void ThrowReadOnly()
  {
    throw new NotSupportedException("Collection is read-only.");
  }
 
  #region IList<T> Members
  T IList<T>.this[int index]
  {
    get
    {
      return this[index];
    }
    set
    {
      ThrowReadOnly();
    }
  }
 
  void IList<T>.Insert(int index, T item)
  {
    ThrowReadOnly();
  }
 
  void IList<T>.RemoveAt(int index)
  {
    ThrowReadOnly();
  }
  #endregion
 
  #region ICollection<T> Members
  void ICollection<T>.Add(T item)
  {
    ThrowReadOnly();
  }
 
  void ICollection<T>.Clear()
  {
    ThrowReadOnly();
  }
 
  bool ICollection<T>.IsReadOnly
  {
    get { return true; }
  }
 
  bool ICollection<T>.Remove(T item)
  {
    ThrowReadOnly();
    return false;   // never reaches here
  }
  #endregion
 
  #region IEnumerable Members
  System.Collections.IEnumerator
      System.Collections.IEnumerable.GetEnumerator()
  {
    return this.GetEnumerator();
  }
  #endregion
 
  #region IList Members
  int IList.Add(object value)
  {
    ThrowReadOnly();
    return -1;      // never reaches here
  }
 
  void IList.Clear()
  {
    ThrowReadOnly();
  }
 
  bool IList.Contains(object value)
  {
    lock (_MirrorLock)
    {
      return ((IList)_MirrorList).Contains(value);
    }
  }
 
  int IList.IndexOf(object value)
  {
    lock (_MirrorLock)
    {
      return ((IList)_MirrorList).IndexOf(value);
    }
  }
 
  void IList.Insert(int index, object value)
  {
    ThrowReadOnly();
  }
 
  bool IList.IsFixedSize
  {
    get { return ((IList)_MirrorList).IsFixedSize; }
  }
 
  bool IList.IsReadOnly
  {
    get { return true; }
  }
 
  void IList.Remove(object value)
  {
    ThrowReadOnly();
  }
 
  void IList.RemoveAt(int index)
  {
    ThrowReadOnly();
  }
 
  object IList.this[int index]
  {
    get
    {
      lock (_MirrorLock)
      {
        return ((IList)_MirrorList)[index];
      }
    }
    set
    {
      ThrowReadOnly();
    }
  }
  #endregion
 
  #region ICollection Members
  void ICollection.CopyTo(Array array, int index)
  {
    lock (_MirrorLock)
    {
      ((IList)_MirrorList).CopyTo(array, index);
    }
  }
 
  bool ICollection.IsSynchronized
  {
    get { return true; }
  }
  #endregion
}

Finally, here’s the tail-end of the class — all the methods we don’t really want to implement, whether because they’re invalid for read-only collections or because they’re not as type-safe as the versions we’ve chosen to implement instead — or just because they’re asking silly questions that are part of our design anyway (the various IsXXX methods). (Still, it’s good that they do exist, although it’s a bit weird to have to define methods only to say “you can’t do that”. At least .NET lets you hide these from the official published interface of the class, so you get the best of both worlds.)

Ok, so that’s the MirroredList. We still haven’t solved the original problem, though. That class allows us to attach one collection to another and have any change to the original collection automatically propagate itself to the second, but everything is all still happening on the same thread. So if we try to bind to the MirroredList right now, we’ll have the same problems as when binding to the original list. So we’re not quite done yet.

The good news, though, is that with the MirroredList in place, getting a binding-safe version is easy:

public class BindableList<T> : MirroredList<T>
{
  private Dispatcher _Dispatcher;
 
  public BindableList(IList<T> baseList)
    : this(baseList, null)
  {
  }
 
  public BindableList(IList<T> baseList, Dispatcher dispatcher)
    : base(baseList)
  {
    _Dispatcher = dispatcher ?? Dispatcher.CurrentDispatcher;
  }
 
  protected override void OnCollectionDirty()
  {
    _Dispatcher.BeginInvoke(DispatcherPriority.Normal,
      new ThreadStart(ProcessChanges));
  }
}

That’s it. Really. The key here, obviously, is the override of OnCollectionDirty, making it call back ProcessChanges on the UI thread at some later point (and not waiting for it to do so, thereby not holding up the collection-changing thread). This will ensure that the mirrored list is only modified by the UI thread itself, which will keep it thread-safe.

In fact you can extend this concept in quite a lot of interesting ways. For example, you could implement “pause” functionality by avoiding the call to ProcessChanges until you unpause. I’m sure you can think of some more. Some things to bear in mind, though:

  • After OnCollectionDirty is called, it will not be called again until after you call ProcessChanges. So if you do want to defer processing until later on, make sure you remember to call it.
  • It’s safe to call ProcessChanges when there are no changes to process; it will just do nothing.
  • If you’ve got multiple threads modifying the base list then any of them could end up running OnCollectionDirty. So don’t make too many assumptions.
  • Changes will continue to accumulate in the background while you’re avoiding calling ProcessChanges. This will consume extra memory, and require more time to “catch up”. If you leave it “paused” for too long, you might even run out. So if you’re going to pause for long periods of time, it might be better to break the link and do a full resynchronise later on.

One last thing before I end this post (which has definitely been my longest one yet): a usage example. Let’s say you have a SynchronizedObservableCollection called WidgetBuildLog. The idea is that whenever your widget-constructing factory completes a step towards building a widget, that is logged into the collection. And for whatever reason (perhaps the protocol you’re using to talk to the widget-building hardware), this collection is getting updated from a different thread than the UI thread. The log always exists and is always getting updated. From your widget factory control application, you can call up a window that displays entries from the build log, but it’s not open all the time. So there’s no need to keep the MirroredList alive the whole time; it only needs to exist when the log window is open.

There are several ways to do this, and the way I’ve chosen to do it is abusing the WPF facilities a little, but it does work and seems reasonably tidy to me.

The first step, of course, is to create your log-displaying window/control.

The next step is to add this to your window/control’s resources:

<ObjectDataProvider x:Key="LogData" MethodName="GetBindableLog"/>

Next, add this to your window/control’s constructor:

ObjectDataProvider data =
    (ObjectDataProvider)FindResource("LogData");
data.ObjectInstance = this;

And then add this method:

public BindableList<WidgetLogItem> GetBindableLog()
{
  return new BindableList<WidgetLogItem>(WidgetBuildLog);
}

You can now data-bind a list control as normal against LogData (by using {StaticResource LogData} as your binding Source), and it will go through the BindableList and thereby permit any thread to modify the WidgetBuildLog despite being shown in the UI. Problem solved.

Ok, in summary once again, while I’ve made every effort to made this as robust as I can, there’s a chance I’ve missed something. Certainly this approach seems easy enough, so I’m not sure why it didn’t make it into the standard framework — after all, I’m assuming that internally WPF keeps a similar mirror copy of bound collections (based on some of the error messages I’ve seen when that goes wrong). And admittedly there are some usage limitations, but nothing that seems too onerous. So who knows?

As usual, please let me know if you find this useful, or see something that ought to be improved, or if you do spot a gaping hole somewhere. And don’t forget the license!

30 comments to Multithreaded Collections and WPF

  • Fantastic article and nice job! I came up with my own solution here:

    http://blog.quantumbitdesigns.com/?p=8

  • Yep, that’s an interesting approach too. I didn’t go that way because I wanted to keep the domain class completely separate from the UI layer; see the example above, where the build log (domain collection) always exists and the mirrored/bindable list only exists while the log viewer window does.

    In the back of my mind I was also thinking about being able to disconnect and reconnect the mirrored list from the base list (to implement long-duration pauses). I didn’t go quite that far in the implementation above but it’s fairly trivial to add, provided that the base list is synchronised (since it will require a Reset).

    I like the graphics, by the way :)

  • Yeah, I could understand why you would want the mirrored list to only exist when the window exists. However, isn’t the memory consumption of the mirrored list extremely small since it is just storing references to the objects that are in the domain collection?

    Also, keep in mind if you change properties of items in the collection from a worker thread, you can run into a WPF bug. It also only manifiests itself on multicore systems, which is scary! The solution/workaround in on my post.

    I like what you did with the queueing of the change requests. I thought about doing that (before reading your article) to improve performance of collections that change a lot. Rather than always BeginInvoke ProcessChanges to the UI thread, if the queue count is > 0 then it does not need to be invoked since the cunsuming thread (UI) is still eating away at the change requests, and the change request producer can just add the change request into the queue. Thats one less message the Dispatcher has to process.

    I’ve never used IWeakEventListener, I’ll have to do some research on it sometime.

  • The memory consumption isn’t really a factor — as you said, it’s fairly minimal. It’s mostly just a separation of concerns thing.

    Which approach makes more sense sort of depends on how you think of the application structure. If you’ve got a predominantly UI-driven design and are using background threads only to increase responsiveness of the UI, then the way you did it makes sense since basically everything is UI-focused anyway (and you’re right, it’s a bit simpler). If you’ve got a task-driven design (where the main goal of the app is to do some work and the UI is only there to monitor the task in progress, then I think the way I did it makes more sense. The app I did this for was of the latter type :)

    I haven’t encountered any problems with property changes on a multicore system thus far, but then in the test app I was using the properties of existing items don’t change very often. I might need to revisit that.

    Actually, you might be surprised about the dispatcher’s performance. According to some simple tests a while back, trying to queue up PropertyChanged notifications and avoid sending duplicates for rapidly-changing properties actually hurts performance. I’m not actually sure whether the queue approach I used is faster than multiple BeginInvokes or not; the design was driven more because I had to serialise processing of NotifyCollectionChangedEventArgs (and abandon pending changes on Reset).

  • You are right, a week or two ago I did some dispatcher performance to determine if it was worth doing the change queue you have, and I was very surprised at how fast the UI could process the messages. So I decided against doing the implementation and blog entry.

    You will probably never see the property/collection change bug on multi core systems unless you really stress these events. If it is of interest to you, I can send you my small and crude test app that causes the failure. Otherwise, happy designing!

  • […] Although I came up with my solution on my own, Miral was the first to post a fantastic write-up on the implications of cross-thread collection binding, […]

  • If you are interested in the sample that can cause a crash on multi-core PCs, here it is:

    http://www.quantumbitdesigns.com/CrossThreadBindingBug.zip

    It only takes a few seconds on my Quad core, but takes up to half an hour on my dual core. I have not tried it on a .NET 3.0 only PC.

  • Seems to die within a few seconds on my (dual core) machine too — and my collection is no more immune to the problem, unfortunately. (Which is not too surprising, since it seems to have been a poor design inside the framework itself.)

    I hope they fix this in a service pack. The workaround you’ve posted seems a bit fragile, since it relies on the collection items to not support the standard INotifyPropertyChanged interface.

    (I guess the main reason why I didn’t come across this myself is that the high-update-frequency app I was testing with didn’t actually modify the properties of the objects once they were in the collection. And I didn’t think that needed testing since we were assured that the framework dealt properly with cross-thread property changes by itself. Guess that goes to show that the old saw about assuming things is still accurate.)

    Incidentally, did you end up filing a bug on this at Connect?

  • I agree that making the collection items support an interface other than INPC is a real bummer, but it is the only workaround I can come up with.

    The bug has been filed at: https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=322818&SiteID=212

    What is interesting is that I actually get quite a few hits to my blog by people searching for the keywords HybridDictionary, ListenerList, Exception, etc. It seems to me many people may be running into this problem. But at the same time, I can’t imaging too many people doing cross thread collection and property binding. Who knows.

  • I’ve actually ended up opening a separate bug report, starting out with reporting the same bug as you, but then it sort of morphed into a related but different bug report πŸ˜‰

    (I did actually do a search for yours first, but didn’t find it. That’s probably because I was in the wrong section [the .NET Framework section] — I didn’t realise there was a separate WPF section.)

    Though the particulars in your report don’t seem correct. For one thing, the problem occurs in the RTM version, not just the beta version, and the weak event managers are thread-local and aren’t shared by more than one thread (except that the event call can occur on any thread) — which incidentally means that the thread that calls RemoveListener must be the same thread that earlier called AddListener.

  • I did not have an option of selecting .NET 3.5 RTM, and even after editing the post, I STILL don’t! :) Oh well.

    As for the weak event managers, good call, I probably should have done my research before making any claims. I have not gone deep into the source, and my claim was based upon feedback from a MS’er from a forum post. Now that the source is able to be easily viewed, it is probably a lot easier to see whats going on in the internals of WPF. Looks like you have been busy digging into the guts of the problem.

    As for your bug report, very nice job. It is very in depth, and you expose many problems.

  • Oh ok. They did the same thing for Blend — they didn’t put the RTM version on the list. Maybe they’re not expecting their release versions to have any bugs πŸ˜›

    I think what happened with the event managers is that they were indeed global static in Beta 2 (when you originally posted on the problem), and the fix that they mention making in that thread for the RTM was to make them thread-local. So we’re both right :) But there’s definitely still something iffy somewhere.

    Now I just hope someone will fix the bug in time for a service pack πŸ˜‰

  • In my bug report, MS said they recognize this as a bug and will fix it in a future version. Nice job us! :)

  • Ditto, which is one of the reasons why I added the warning at the top of the post :)

  • Ah yes, of course. I read that but obviously I was not paying attention. I just knew you filed a different report than me (but I didn’t remember the details of yours).

  • Oren Novotny

    A quick test on a quad-core .NET 3.5 SP1 beta did not appear to crash? Can either of you confirm?

  • Interesting. I haven’t picked up SP1 yet, but they did say they were trying to get the fix into there. I’ll have to give it a try sometime, though I usually wait for the RTM.

    (If you’re feeling enthused, then I can send you one of the test cases I attached to my bug report. It doesn’t look like they’re downloadable from the bug report itself.)

  • I won’t be installing the beta of SP1… so I am unable to confirm. MS claimed they would fix it in a later release, so perhaps 3.5 SP1 includes the fix.

  • davernginzks

    I’ve recently joined and wanted to introduce myself :)

  • Skiff

    Hi, just tried your test app with the RTM 3.1 SP1, and it now seems to keep running (3hrs and still going), whereas it fell over after a few minutes on the same machine pre SP1.

  • Good to know. One of these days I’ll get around to testing it myself :) (Hopefully it won’t be too much longer before the project I developed this for migrates to .NET 3.5…)

  • […] and possible ways to get it better. Note: the ideas for the mirrorcollection class are taken form here and here, althoug the problen that they focus is quite different: in short it is that WPF does not […]

  • […] and possible ways to get it better. Note: the ideas for the mirrorcollection class are taken form here and here, althoug the problen that they focus is quite different: in short it is that WPF does not […]

  • […] a long-overdue reboot of the child window model coupled with a powerful data binding engine (though not without its own quirks). And I love […]

  • ambivepreesia

    This looks cool so far, what’s up people?
    If it’s not just all bots here, let me know. I’m looking to network
    Oh, and yes I’m a real person LOL.

    Peace,

  • Psytronic

    Hey,

    Thanks for the great tutorial, just what I needed *thumbs up*

    However I do seem to be having a bit of trouble implementing it. I’ve got all the classes and everything, however the method defined in the ODP MethodName is not called. The only way I found to get it called was to declare ObjectType in the ODP, and point it to my object class, however putting the GetBindableLog method in there does not work as it is calling our SynchronizedObservableCollection, which does not exist in that class?

    Hope you can help, Psy

  • Did you set the ObjectInstance from code? ObjectInstance and ObjectType are incompatible (the former tells it to use an existing instance while the latter tells it to create a new instance). And note that the GetBindableLog method should be on the Window, not on the domain collection (so you definitely don’t want it creating a new instance of the window).

    Another option, of course, is to use a property on the window itself to bind to the collection; define a dependency property on the window class, assign the bindable list to it in the window constructor, and bind to that property from the XAML. In some ways it’s a little more annoying than going through the ODP, but I probably end up doing this more often than using an ODP nowadays.

    And note that if you’re using a MVC/MVP type architecture, the BindableList creation belongs to the view, not to the model/presenter/controller. It’s purely a UI artifact. The underlying collection, on the other hand, does belong in the model layer (or possibly presenter/controller, if it’s something more transient).

  • […] and possible ways to get it better. Note: the ideas for the mirrorcollection class are taken form here and here, althoug the problen that they focus is quite different: in short it is that WPF does not […]

  • Observable Collection Projections (Synchronized MVVM Collections)…

    (Category: Rambling) Sometimes it’s funny how the same simple problem comes up over and over again, and…

  • […] support updating the OC on a secondary thread. I found a number of posts on the topic, like this one from Mirality, and I will be looking into those […]

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>