-1

The suggested duplicate thread did not address my question

So it is my understanding that a WPF application handles everything UI related, button presses, updates to observable collections through the dispatcher thread, which can be called with Application.Current.Dispatcher.Invoke(Update UI element here), while changes to models and data can be handled by background threads normally.

What I don't understand is why you need to call the dispatcher for say updating an observable collection Bound to a Combobox, but yet when I want to update the progress bar or text in a textbox or if a button is enabled I don't need to call the dispatcher. Everything I read states that the dispatcher is used for handling and updating the UI. Are textboxes, the status of progress bars, and whether or not a button is enabled not see as UI?

What is the difference between an Observable collection and text/progress bars that makes calling the dispatcher not required?

Moon
  • 85
  • 1
  • 5

1 Answers1

1

Almost any call to a WPF UI element should happen on the main thread of the application. This is usually done through the methods of the Dispatcher associated with this thread. The dispatcher can be obtained from any UI element, but usually it is obtained from the application: Applicatiion.Current.Dispatcher.

If you do not assign values ​​directly to the properties of UI elements, but use bindings for this, then the bindings mechanism has built-in marshaling of value assignment into the flow of the UI element. Therefore, the INotifyPropertyChanged.PropertyChanged event can be raised on any thread.

When the observable collection changes, the INotifyCollectionChanged.CollectionChanged event is raised. It is not provided for automatic marshaling to the UI thread. But the collection can be synchronized with the BindingOperations.EnableCollection (...) method. Then it can be changed (using synchronization) in any thread. If such synchronization has not been performed, then it can be changed only in the UI thread.

In addition to these events, there is also ICommand.CanExecuteChanged. There are no additional marshaling or synchronization methods provided for it. Therefore, it can be raised only in the UI thread. This is usually built into the WPF implementation of ICommand and the programmer doesn't need to worry about it. But in simple (mostly educational) implementations, there is no such marshaling. Therefore, when using them, the programmer himself must take care of the transition to the UI thread to raise this event.

So basically in MVVM practice you no matter what you would have to use a dispatcher to use BindingOperations.EnableCollectionSynchronization(fooCollection, _mylock)); correct?

Yes. For the application of the collection, you understood correctly.

Here only the problem of separation of the View and ViewModel functionality arises. You can only call "EnableCollectionSynchronization" on the View or on the main UI thread. But you are implementing the collection in the ViewModel. Also, in order not to clog up memory, when deleting a collection (replacing it with another, clearing the bindings that use it, replacing the ViewModel instance, etc.), you need to delete the created synchronization using the "DisableCollectionSynchronization (collection)" method.

In this regard, if a single instance of the ViewModel is used throughout the application session, then using "EnableCollectionSynchronization ()" is the simplest solution.

Example:

    public class MainViewModel
    {
        public ObservableCollection<int> Numbers { get; }
            = new ObservableCollection<int>();

        protected static readonly Dispatcher Dispatcher = Application.Current.Dispatcher;
        public MainViewModel()
        {
            if (Dispatcher.CheckAccess())
            {
                BindingOperations.EnableCollectionSynchronization(Numbers, ((ICollection)Numbers).SyncRoot);
            }
            else
            {
                Dispatcher.Invoke(()=>BindingOperations.EnableCollectionSynchronization(Numbers, ((ICollection)Numbers).SyncRoot));
            }
        }
    }

But if many VM instances are used, with mutual nesting and dynamic addition and removal (for example, this can be the case when implementing a tree and viewing it in a TreeView), then using "EnableCollectionSynchronization ()" becomes not trivial. If you do as I showed in the example above, then due to the fact that the reference to the synchronization object, to the collection, will be saved, they will not be deleted from memory and, accordingly, unnecessary instances of ViewModel will not be deleted. And this will lead to a memory leak. Therefore, in practice, marshaling of collection changes to the UI thread is often used.

It is also possible to embed in the INotifyCollectionChanged implementation, as well as marshaling to the UI thread, and calling "EnableCollectionSynchronization () / DisableCollectionSynchronization ()" while subscribing / unsubscribing to the CollectionChanged event. The implementation of the ObservableCollection does not have this, but there are custom implementations of the observable collections in various packages where the similar is implemented. And their use frees the programmer from creating routine, specific, repetitive code.

Unfortunately, I cannot tell you exactly what the package contains the required implementation. I prefer to use my own implementations.

EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • So basically in MVVM practice you no matter what you would have to use a dispatcher to use `BindingOperations.EnableCollectionSynchronization(fooCollection, _mylock));` correct? It's just easier to throw all your enable collection synchronizations collections methods into one dispatcher call than by using a dispatcher call every single time. Did I understand that correctly? – Moon Sep 06 '21 at 00:10
  • I supplemented my answer. Read this it. – EldHasp Sep 06 '21 at 06:42
  • @EldHasp In your discussion of memory leaks above with the "MainViewModel" example, are those memory leaks fully fixed by rigorously calling DisableCollectionSynchronization, or are there some edge cases where DisableCollectionSynchronization doesn't fully free objects for collection? I think I have seen memory leaks even when DisableCollectionSynchronization is called propertly, but I am having trouble reproducing that in a sample application. – PatrickV May 15 '22 at 17:59
  • 1
    @PatrickV, I can't give an answer without seeing the code. Perhaps you have several methods `EnableCollectionSynchronization` called for one collection, and the method `DisableCollectionSynchronization` only once. Or you are replacing the collection instance in the property with another one, and method `DisableCollectionSynchronization` is not called on the old instance. – EldHasp May 16 '22 at 11:02
  • @EldHasp something to think about, as we have a lot of complexity. But I *think* the only difference between two test cases is our call to EnableCollectionSynchronization. If we pass in the collection as the lockObject parameter, we get leaks that we do not get if we pass in a [new object()] as that parameter. But we only see that in our very-not-clean application and I have failed to reproduce it in test applications. – PatrickV May 16 '22 at 13:03
  • 1
    @PatrickV, ICollection.SyncRoot is the typical synchronization object commonly used in .Net. Of course, it can be replaced by an external synchronizer, but this solution is less transparent and less understood by other people. It doesn't matter which method you choose for the `Enable` method. The reference to the collection will be held until the `Disable` method is called, to which only the collection itself is passed. Therefore, the type of synchronizer, for this problem, does not matter. – EldHasp May 16 '22 at 16:03
  • 1
    ICollection.SyncRoot is more convenient to use because it is "hard-wired" to the collection instance. For example, to make calling Enabled/Disabled easier, you can make extension methods on ICollection. – EldHasp May 16 '22 at 16:14
  • 1
    You can also make your own implementation of ObservableCollection, which will automatically call these methods for its listeners. And keep in mind that using Enabled / Disabled does not completely solve all problems. If the GUI update coincides with the collection update, then exceptions can still be thrown. Therefore, when updating a collection or calling its IEnumerator, it is better to first lock the collection on the synchronization object. – EldHasp May 16 '22 at 16:14
  • @EldHasp good advice, and it all makes sense. I'm still not sure why we seem to leak with one but not the other. It is probably that the system is doing something I'm not aware it is doing. – PatrickV May 17 '22 at 18:43
  • 1
    @PatrickV, Memory leaks are always hard to find. Too many different non-obvious problems can be. They can only be detected by painstaking debugging in Debug. Try to create your own collection class with an added finalizer (destructor) in which the message will be displayed. And where you think you've removed all references to the collection, call GC.Collect(). If the message is displayed, then this was indeed the last link. If not, then you need to look for where the link still remains. – EldHasp May 17 '22 at 19:29