Why aren't classes like BindingList or ObservableCollection thread-safe?
Asked Answered
P

4

25

Time and time again I find myself having to write thread-safe versions of BindingList and ObservableCollection because, when bound to UI, these controls cannot be changed from multiple threads. What I'm trying to understand is why this is the case - is it a design fault or is this behavior intentional?

Pfeffer answered 9/2, 2009 at 17:5 Comment(0)
C
32

The problem is designing a thread safe collection is not simple. Sure it's simple enough to design a collection which can be modified/read from multiple threads without corrupting state. But it's much more difficult to design a collection that is usable given that it's updated from multiple threads. Take the following code as an example.

if ( myCollection.Count > 0 ) {
  var x = myCollection[0];
}

Assume that myCollection is a thread safe collection where adds and updates are guaranteed not to corrupt state. This code is not thread safe and is a race condition.

Why? Even though myCollection is safe, there is no guarantee that a change does not occur between the two method calls to myCollection: namedly Count and the indexer. Another thread can come in and remove all elements between these calls.

This type of problem makes using a collection of this type quite frankly a nightmare. You can't ever let the return value of one call influence a subsequent call on the collection.

EDIT

I expanded this discussion on a recent blog post: http://blogs.msdn.com/jaredpar/archive/2009/02/11/why-are-thread-safe-collections-so-hard.aspx

Cheri answered 9/2, 2009 at 17:13 Comment(3)
I have the exact same problem. How do I use the Dispatcher or else to add an item to my BindingList from a background thread?Fabiola
Yes, In short, the design of the .NET collection interfaces themselves is/are not appropriate for thread safety. As Jared points out for example, the Count property is useless in a multi-threaded environment.Comptom
There are many useful subsets of IList<T> functionality which could be implemented in thread-safe fashion. The items that remove things or move things around would be problematic, but the rest of the interface would be a useful subset for many applications. A version of Add which reported the index of the item added would be helpful, but not all applications would need it. If every item that is ever added will continue to exist in the same slot for the life of the list, a thread-safe IList<T> implementation could be useful in many multi-threaded scenarios without external locking.Embolectomy
V
6

To add a little to Jared's excellent answer: thread safety does not come for free. Many (most?) collections are only used within a single thread. Why should those collections have performance or functionality penalties to cope with the multi-threaded case?

Voltmer answered 9/2, 2009 at 17:20 Comment(2)
Well, maybe I should rephrase it then: why doesn't the framework provide a ThreadSafeObservableCollection or some such?Pfeffer
That's a more reasonable question - but then Jared's answer kicks in. It really depends on what you mean by "thread-safe" - which is not a simple yes/no flag.Voltmer
D
5

Gathering ideas from all the other answers, I think this is the simplest way to resolve your issues:

Change your question from:

"Why isn't class X sane?"

to

"What is the sane way of doing this with class X?"

  1. in your class's constructor, get the current displatcher as you create your observable collections. Becuase, as you pointed out, modification need to be done on the original thread, which may not be the main GUI thread. So App.Current.Dispatcher isn't alwasys right, and not all classes have a this.Dispatcher.

    _dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher;
    _data = new ObservableCollection<MyDataItemClass>();
    
  2. Use the dispatcher to Invoke your code sections that need the original thread.

    _dispatcher.Invoke(new Action(() => { _data.Add(dataItem); }));
    

That should do the trick for you. Though there are situations you might prefer .BeginInvoke instead of .Invoke.

Derwin answered 14/11, 2012 at 23:48 Comment(0)
G
2

If you want to go crazy - here's a ThreadedBindingList<T> that does notifications back on the UI thread automatically. However, it would still only be safe for one thread to be making updates etc at a time.

Giacinta answered 9/2, 2009 at 20:21 Comment(1)
This implementation only marshals 'adds' to the sync context thread: does not protect against an number of other race conditions and/or collection modified errors when enumerating the list.Turgot

© 2022 - 2024 — McMap. All rights reserved.