Why aren't classes like BindingList or Observa

2019-03-09 01:59发布

问题:

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?

回答1:

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



回答2:

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?



回答3:

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.



回答4:

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.