How to update ObservableCollection using workflow?

I have an ObservableCollection<A> a_collection; The collection contains the elements "n". Each element of A is as follows:

 public class A : INotifyPropertyChanged { public ObservableCollection<B> b_subcollection; Thread m_worker; } 

In principle, all this is connected with the WPF + presentation by the detail view control, which shows the b_subcollection of the selected element in a separate list (2-way bindings, updates to propertychanged, etc.). A problem arose for me when I started to execute threads. The whole idea was for the whole a_collection to use the workflow to "get the job done" and then update their respective b_subcollections and display the results in real time in real time.

When I tried this, I got an exception saying that only the Dispatcher thread can modify the ObservableCollection, and work stopped.

Can someone explain the problem and how to get around it?

Greetings

+60
multithreading c # wpf observablecollection
Jan 19 '10 at 8:07
source share
4 answers

Technically, the problem is not that you are updating the ObservableCollection from the background thread. The problem is that when you do this, the collection fires the CollectionChanged event in the same thread that caused the change - which means that the controls are updated from the background thread.

To populate a collection from a background thread when the controls are attached to it, you may have to create your own collection type from scratch to solve this problem. There is a simpler option that may work for you.

Publish Add calls to the user interface thread.

 public static void AddOnUI<T>(this ICollection<T> collection, T item) { Action<T> addMethod = collection.Add; Application.Current.Dispatcher.BeginInvoke( addMethod, item ); } ... b_subcollection.AddOnUI(new B()); 

This method will be returned immediately (before the item is added to the collection), and then in the user interface thread the item will be added to the collection, and everyone should be happy.

The reality, however, is that this solution is likely to face a heavy load due to all the cross-flow activity. A more efficient solution is to start a bunch of elements and periodically publish them to the user interface thread so that you don't call threads for each element.

The BackgroundWorker class implements a template that allows you to report progress through ReportProgress during a background operation. Progress is reported in the user interface thread through the ProgressChanged event. This may be another option for you.

+60
Jan 19 '10 at 8:17
source share

New option for .NET 4.5

Starting with .NET 4.5, there is a built-in mechanism for automatically synchronizing access to collections and sending CollectionChanged events to the user interface stream. To enable this function, you need to call BindingOperations.EnableCollectionSynchronization from your UI thread.

EnableCollectionSynchronization does two things:

  • It remembers the stream from which it is called, and causes the data binding pipeline to move CollectionChanged events in that stream.
  • Gets the collection lock until the processed event is processed, so that the event handlers that trigger the UI thread will not try to read the collection when it is modified from the background thread.

It is very important, this does not take care of everything : to provide thread-safe access to an insecure collection , you need to cooperate by getting the same lock from the background threads when the collection is modified.

Therefore, to work properly, the following steps are required:

1. Decide which lock you will use.

This will determine which EnableCollectionSynchronization overload should be used. In most cases, a simple lock statement will suffice, so this overload is the standard choice, but if you use some kind of fantastic synchronization mechanism, there is also support for custom locks .

2. Create an assembly and enable synchronization

Depending on the lock mechanism selected, invoke the corresponding overload in the user interface thread . If you use the standard lock statement, you need to provide a lock object as an argument. If you use custom synchronization, you need to provide a CollectionSynchronizationCallback delegate and a context object (which may be null ). When invoked, this delegate should receive your custom lock, invoke the Action passed to it, and release the lock before returning.

3. Collaborate by locking the collection until it is modified

You must also lock the collection using the same mechanism when you intend to change it yourself; do this with lock() in the same lock object that was passed to EnableCollectionSynchronization in a simple script, or with the same custom synchronization mechanism in a user script.

+99
Jan 30 '13 at 10:46
source share

With .NET 4.0, you can use these single-line characters:

.Add

 Application.Current.Dispatcher.BeginInvoke(new Action(() => this.MyObservableCollection.Add(myItem))); 

.Remove

 Application.Current.Dispatcher.BeginInvoke(new Func<bool>(() => this.MyObservableCollection.Remove(myItem))); 
+15
Jan 22 '15 at 11:16
source share

Collection synchronization code for posterity. To do this, use a simple locking mechanism to synchronize the collection. Note that you need to enable collection synchronization in the user interface thread.

 public class MainVm { private ObservableCollection<MiniVm> _collectionOfObjects; private readonly object _collectionOfObjectsSync = new object(); public MainVm() { _collectionOfObjects = new ObservableCollection<MiniVm>(); // Collection Sync should be enabled from the UI thread. Rest of the collection access can be done on any thread Application.Current.Dispatcher.BeginInvoke(new Action(() => { BindingOperations.EnableCollectionSynchronization(_collectionOfObjects, _collectionOfObjectsSync); })); } /// <summary> /// A different thread can access the collection through this method /// </summary> /// <param name="newMiniVm">The new mini vm to add to observable collection</param> private void AddMiniVm(MiniVm newMiniVm) { lock (_collectionOfObjectsSync) { _collectionOfObjects.Insert(0, newMiniVm); } } } 
0
Aug 17 '18 at 19:58
source share



All Articles