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?