multi-thread friendliness

Jan 6, 2010 at 10:52 PM

I was so excited to use SelectMany, then the dreaded "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread." descended.  I tried to modify FireCollectionChanged to iterate over the CollectionChanged invocation list and invoke handling on their own thread, but that just led to other issues.

Obviously, I could look into removing all threading from my application, but I don't think that's feasible at this time.  Have you given any thought to this problem, is there a solution for me?

Thanks.

Mar 2, 2010 at 3:06 PM

I am seeing this very same issue. The problem is when you bind a collection to an ItemsControl in WPF, it generates an ICollectionView instance for it.  The views are a UI concept and, as such, have a connection to the owner UI thread. Thus, if you try to modify the collection view from a non-UI thread, you'll see this type of exception. The problem lies in the fact that although ContinuousCollection<T> knows about the Dispatcher and is able to behave correctly when called from any thread, ReadOnlyContinuousCollection<T> is not aware of the Dispatcher and thus executes all notifications on the calling thread.

The fix that has worked for me was to modify the ReadOnlyContinuousCollection<T> class as follows: 

  1. Add a field:
    private readonly Dispatcher dispatcher;
    
  2. Create a default constructor:
    protected ReadOnlyContinuousCollection()
    {
        dispatcher = Dispatcher.CurrentDispatcher;
    }
    
  3. Modify FireCollectionChanged to execute on the dispatcher.
    Action action = () =>
                        {
                            if (CollectionChanged != null)
                            {
                                CollectionChanged(this, args);
                            }
    
                            if (args.Action != NotifyCollectionChangedAction.Replace)
                            {
                                OnPropertyChanged("Count");
                            }
                        };
             
    dispatcher.BeginInvoke(DispatcherPriority.Normal, action);

Hope this helps temporarily, but I am hoping for a solution from the CLinq guys for this as well.

- Szymon

Mar 2, 2010 at 8:32 PM

One note: the solution above will help with the immediate problem you are seeing.

The fact, however, is that CLinq may not be thread safe in other ways. One problem we've ran into is that if:

  1. We have a ContinuousCollection<DataObject>
  2. We have a set of Continuous aggregations and queries on top of that collection
  3. DataObject implements INotifyPropertyChanged

We updated the properties of our set of DataObjects from different threads simultaneously and we quickly ran into exceptions being thrown internally in the CLinq code in which collection views were unable to find items at specified positions and other exceptions. Getting rid of multi-threaded data object updates got rid of these.

I'd wait for a thread-safe release before going with full on multi threaded CRUD operations on the collection and data objects.

-Szymon

Coordinator
Mar 3, 2010 at 7:28 PM

Hi all,

As always, I take too long to reply to these threads.  The multithreading problem is a hard one for this library.  The reason being is that notification and access are not atomic unless you do Invokes rather than BeginInvokes.  This means that by the time the handler is able to read the data, the data may have changed again or if the clause of a query accesses the data multiple times then things could change between accesses.  Imagine if you have a Where(obj => obj.Friend != null && obj.Friend.Name == "Foo").  If there is a context switch after the null check and someone sets the Friend property to null, then a null reference exception will be thrown. In my experience the best way to handle cross thread communication is via message passing or ownership passing.  Attempting to put in a locking mechanism for this would probably not work out either. 

The best way to deal with this problem right now is to make your queries "live" on a single thread and marshal things to that thread.  I could, write something that will be a cross thread collection change bridge so that you can define a query on a background thread:

ReadOnlyObservableCollection<Person> ninjas = peopleCollection.Where( person => person.Title = "Ninja" );

Then, you would define a collection that you could then data bind to, or safely access from a different thread.  (Let's just say this is the UI thread for argument's sake.)

ReadOnlyObservableCollection<Person> ninjasForThisThread = ninjas.MarshallToThisThread();

Under the covers, each thread would store it's own copy of the data.  The query logic would only run on the background thread, and as things are added or removed from the collection on the background thread, they are then marshalled to the UI thread.  To the UI thread it would appear exactly the same. 

Now... the only catch is that items in the collection that is on the background thread still cannot be modified on any other thread.  For example, if you have two worker threads: thread A, and thread B, and you defined ninja query on thread A, you still cannot update a person in the peopleCollection on Thread B without bad things happening.  The reason being is laid out above.

What do you guys think?  Coding the marshaller up shouldn't be too painful, but if the above solution won't help, let me know. 

I am porting to Silverlight right now which is now going to be a new set of challenges as there is only one dispatcher in the whole system, and apps can't create Dispatchers for worker threads.

 

Another thing to consider for the property change marshalling.  (does not affect collection change really)

Let's say you update 3000 objects that are being monitored by a query.  If you put some kind of IsInvokeRequired in your OnPropertyChanged then you're in for 3000 dispatches across threads.  So how do you batch these up?  INotifyPropertyChanged uses C# eventing system so I would need people to put some kind of "QueuePropertyChanged" into their property setters which I think sucks.  On the other hand... ContinuousCollection has AddRange and RemoveRange which does do batching, but that only helps for collection changes.

Before I go further... can anyone tell me what perf bottlenecks they've hit with CLINQ that has pushed these types of operations to a background thread?  The reason I ask, is that the main app at my company that puts CLINQ through it's paces uses thousands of objects with hundreds of live queries that all live on the UI thread.  If you guys are exercising the system in a different way, perhaps I can change CLINQ to accommodate with

Thanks,

AK (your humble CLINQ narrator)

 

May 10, 2010 at 3:57 PM

Hei, nice tool!

Not necessary on the same thread topic but I belive it kind of overlaps...

My suggestion would be the following:

- Add another constructor to  the ContinousCollection Class that accepts a Dispatcher. Int his way your ContinousCollection becomes more flexible.

For argument sake, lets assume I have a DataStore which is in fact a singleton - thus not using the UI thread - and I setup several properties whcih are ContinuousColelction of type XZY and background threads are responsible to update it, having the ViewModels consume these collections...If I can inject the UIThread then it makes "life" easier as I no longer use the current thread but force it to usee the UI Thread - but still capable of using the default parameterless constructor.

I understand that there is a cost for this, but then again the collection already performs thread switching so I do not see any changes on that end.

Food for thought I guess...