ManualResetEvent WaitOne blocks the Thread owner of my CollectionView

I wrote a WPF WizardFramework that performs some actions in the background using BackgroundWorker . During processing, it may happen that I need to update an ObservableCollection tied to my user interface.

In this case, I wrote a ThreadableObservableCollection that provides threading methods for Insert , Remove and RemoveAt . Although I am using .NET 4.5, I have not been able to get BindingOperations.EnableCollectionSynchronization to work without many other invalid access exceptions. My Collection looks like this:

  public class ThreadableObservableCollection<T> : ObservableCollection<T> { private readonly Dispatcher _dispatcher; public ThreadableObservableCollection() { _dispatcher = Dispatcher.CurrentDispatcher; } public void ThreadsafeInsert(int pos, T item, Action callback) { if (_dispatcher.CheckAccess()) { Insert(pos, item); callback(); } else { _dispatcher.Invoke(() => { Insert(pos, item); callback(); }); } } [..] } 

This works as expected while I use the wizard in my application. Now I use NUnit to write integration tests for the application.

There is a listener here who is waiting for the WizardViewModel to complete its work and looking for some pages that are entered in the Collection template. After shutting down asyncrone, I can use Validate to check the viewmodel status.

Unfortunately, I use ManualResetEvent to wait for the wizard to close. It looks like this:

  public class WizardValidator : IValidator, IDisposable { private WizardViewModel _dialog; private readonly ManualResetEvent _dialogClosed = new ManualResetEvent(false); [..] public void ListenTo(WizardViewModel dialog) { _dialog = dialog; dialog.RequestClose += (sender, args) => _dialogClosed.Set(); dialog.StepsDefaultView.CurrentChanged += StepsDefaultViewOnCurrentChanged; _dialogClosed.WaitOne(); } [..] } 

Now the problem arises: While the application is running, the user interface thread is not blocked, the collection can be updated without any problems. But in my test boxes, the β€œmain” thread where I initialize the ViewModel (and because of this the collections) is AppDomainThread, which is blocked by the test code. Now my ThreadsafeInsert wants to update the collection, but cannot use the AppDomain Thread.

But I have to wait for the wizard to finish, how can I solve this dead end? Or is there a more elegant solution for this?

edit I worked on this problem with checking if there is a user interface, and only then I call on Application-Thread, otherwise I intentionally change the collection to another thread. This does not prevent the exception, but it is not recognized from the test ... however, the elements are inserted, but only the NotifyCollectionChanged -Handler is not called (which is used only in the user interface).

  if (Application.Current != null) { Application.Current.Dispatcher.Invoke(() => { Steps.Insert(pos, step); stepsView.MoveCurrentTo(step); }); } else { new Action(() => Steps.Insert(pos, step)).BeginInvoke(ar => stepsView.MoveCurrentToPosition(pos), null); } 

This is an ugly workaround, and I'm still interested in a clean solution.

Is there a way to use an alternative dispatcher to create (e.g.) an entire ViewModel and use it to modify my collection?

+6
source share
2 answers

How do I see the main problem that the main thread is blocked, and other operations are also performed in the main thread? How about not blocking the main thread, for example:

 // helper functions public void DoEvents() { DispatcherFrame frame = new DispatcherFrame(); Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame); Dispatcher.PushFrame(frame); } public object ExitFrame(object f) { ((DispatcherFrame)f).Continue = false; return null; } // in your code: while(!_dialogClosed.WaitOne(200)) DoEvents(); 

If this does not help, I think you need to try some workarounds for the SynchronisationContext.

+3
source

I think the problems boil down to the fact that you are creating an ObservableCollection bound to a Dispatcher object.

Attracting a Dispatcher object directly is almost never a good idea (as you just saw). Instead, I suggest you look at how others have implemented ThreadSafeObservableCollection. This is a small example that I put together, this should illustrate the point:

 public class ThreadSafeObservableCollection<T> : ObservableCollection<T> { private readonly object _lock = new object(); public ThreadSafeObservableCollection() { BindingOperations.CollectionRegistering += CollectionRegistering; } protected override void InsertItem(int index, T item) { lock (_lock) { base.InsertItem(index, item); } } private void CollectionRegistering(object sender, CollectionRegisteringEventArgs e) { if (e.Collection == this) BindingOperations.EnableCollectionSynchronization(this, _lock); } } 
0
source

All Articles