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


  • Catchable fatal error: Object of class WP_Comment could not be converted to string in /home/uecasm/public_html/lambert/wpdata/wp-content/themes/atahualpa/functions/bfa_custom_comments.php on line 16