Updating ObservableCollection on dispatcher thread causes UI to freeze - c#

When updating my ObservableCollection, I was getting this error:
This type of CollectionView does not support changes to its
SourceCollection from a thread different from the Dispatcher thread.
Using this answer as a guide, I thought this code would work:
private ObservableCollection<string> _userMessages = new ObservableCollection<string>();
public void AddUserMessage(string message)
{
lock (_locker)
{
Action action = () =>
{
this._userMessages.Add(message);
};
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, action);
}
}
However, my UI now freezes when calling Dispatcher.Invoke(). What am I doing incorrectly?
Note: I needed to do this because I'm (sometimes) updating my ObservableCollection from events.

Try this:
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, action);
You invoke your action synchronously and it causes UI to freeze.

Related

WPF update observableCollection in UI thread

I have WinForms with WPF UserControl.
public partial class DynamicMediator : Form
{
public DynamicMediator()
{
InitializeComponent();
lmDynamicMediator = new LMDynamicMediator.MediatorMainWindow();
this.elementHost1.Child = this.lmDynamicMediator;
}
public MainWindowViewModel GetEditFormViewModel()
{
return lmDynamicMediator.Resources["ViewModel"] as MainWindowViewModel;
}
}
I start a new process in my ViewModel
after that I need to update my observableCollection in ViewModel
I use
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => HasListMessages.Add(item)));
but I have exception like this
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread
if I use code like this
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => HasListMessages.Add(item)));
I have exception like this
System.Windows.Application.Current.get returned null
What I do wrong?
How I can get System.Windows.Application.Current in my ViwModel
Calling Dispatcher.CurrentDispatcher in a thread that is not yet associated with a Dispatcher will create a new Dispatcher, which is not the Dispatcher of the UI thread.
Use the Dispatcher of the thread in which the view model instance is created. Keep it in a field of the view model class
public class MainWindowViewModel
{
private readonly Dispatcher dispatcher;
public MainWindowViewModel()
{
dispatcher = Dispatcher.CurrentDispatcher;
}
...
}
and later use that field:
dispatcher.BeginInvoke(() => HasListMessages.Add(item));

Async and Dispatcher explanation

I have a class using the code below. Additional I'm using another class to check the availability of my network connection. This class provides an event for that, which I'm using to start and stop a timer.
If the event gets fired, I always get an error message
The application called an interface that was marshalled for a different thread
public class ViewModel : BindableBase
{
private DispatcherTimer timerUpdate = null;
public ViewModel()
{
NetworkAvailabilty.Instance.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
timerUpdate = new DispatcherTimer();
timerUpdate.Interval = TimeSpan.FromMinutes(15);
timerUpdate.Tick += timerUpdate_Tick;
if (NetworkAvailabilty.Instance.IsNetworkAvailable)
{
timerUpdate.Start();
}
}
private void timerUpdate_Tick(object sender, object e)
{
// do something
}
public void OnNetworkAvailabilityChanged(object source, EventArgs e)
{
if (NetworkAvailabilty.Instance.IsNetworkAvailable)
{
timerUpdate.Start();
}
else
{
timerUpdate.Stop();
}
}
}
So after some research I fixed the issue using the following code:
public void OnNetworkAvailabilityChanged(object source, EventArgs e)
{
if (NetworkAvailabilty.Instance.IsNetworkAvailable)
{
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
timerUpdate.Start();
});
}
else
{
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
timerUpdate.Stop();
});
}
}
But what's behind that issue and how doeas this part of the code prevents this error message?
Even here Visual Studio tells me that I should consider to use await in front of Windows.ApplicationModel.....
But what's behind that issue
DispatcherTimer is created on the UI thread, and it is what is called a "thread-affine" object (just like most UI components). This means that it "binds" to its UI thread and now belongs to it.
NetworkAvailabilty is not a UI component, and it always raises its NetworkAvailabilityChanged event on a thread pool thread.
Thus, OnNetworkAvailabilityChanged is running on a thread pool thread, and it tries to access the DispatcherTimer which is bound to the UI thread. That's what causes the exception:
The application called an interface that was marshalled for a different thread
It's saying that DispatcherTimer is marshalled for the UI thread (and thus bound to the UI thread), but your app is invoking one of it's methods from a thread pool thread.
how doeas this part of the code prevents this error message?
RunAsync executes its delegate on the UI thread.
Personally, I prefer to use SynchronizationContext instead of Dispatcher/CoreDispatcher. If you do use the dispatcher, you should await the call and make OnNetworkAvailabilityChanged an async void event handler.

ManualResetEvent WaitOne blocks the owner Thread of my CollectionView

I've written a WPF WizardFramework which performs some actions in the background using some BackgroundWorker. While processing it can happen that I have to update an ObservableCollection which is bound to my UI.
For this case I've written a ThreadableObservableCollection, which provides threadsafe methods for Insert, Remove and RemoveAt. Though I'm using .NET 4.5 I was not able to get BindingOperations.EnableCollectionSynchronization working without many other invalid access exceptions. My Collection looks like:
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 is working as expected, while I am using the wizard in my application. Now I'm using NUnit to write some integrationtests for the application.
There's a listener which waits for the WizardViewModel to finish it's work and looking for some pages which are injected in the Steps-Collection. After the asyncrone work is done I can use Validate to check the viewmodel state.
Unfortunately I'm using a ManualResetEvent to wait for the wizard to close. This looks like following:
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 there's a problem:
While the Application is running the UI Thread is not blocked, the Collection can be updated without any problems. But in my testcases the "main" Thread where I initialize the ViewModel (and because of that the Collections) is an AppDomainThread which is blocked by the testcode. 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 kind of deadlock? Or is there a more elegant solution for this one?
edit:
I worked around this problem with a check if there's a user interface, and only then I invoke on the Application-Thread, otherwise I change the collection intentionally on another thread. This does not prevent the exception, but it is not recognized from the test... the items are inserted nevertheless, only the NotifyCollectionChanged-Handler is not called (which is only used in the UI anyway).
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 am still interested in a clean solution.
Is there a way to use an alternate Dispatcher to create (e.g.) the whole ViewModel and use this to change my collection?
As I see the main problem that main thread is blocked and other operations are trying to be executed in main thread too? What about not to block main thread, like this:
// 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 it will not help then I guess need to try some SynchronisationContext workarounds.
I think the problems boil down to the fact that you create ObservableCollection that is tied to Dispatcher object.
Involving Dispatcher object directly is almost never good idea(as you just witnessed). Instead I would suggest you to see how others have implemented ThreadSafeObservableCollection. This is a little example I put together, it 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);
}
}

Update bound control from own background thread

I have WPF with MVVM app. ViewModel and View are connected. Controls from View are bound to ViewModel, and ViewModel inherits INotifyChanged. Simple control update in ViewModel works fine.
I'd like to create some info class that contains some info fields. Next I want to create my own thread that maps fields from Info class to fields in ViewModel that updates View. Object of Info class will be used as arg of function called in ViewModel.
private int someControl;
public SomeControl {
get{return someControl;}
set
{
someControl = value;
OnPropChanged("SomeControl");
}
private InfoClass info = new InfoClass();
Thread thread = null;
public ViewModel()
{
Thread thread = new Thread(new ThreadStart(update));
thread.IsBackground = true;
thread.start();
someLongFunction(info);
}
private void update()
{
SomeControl = info.someField;
thread.sleep(1000);
update();
}
What should I add or change to get update periodically? Now, update is only when someLongFunction ends its job.
If what you actually want is continuous view updates then you don't have to create a thread for that because then you will have to make those updates on the Dispatcher thread (UI Thread). Instead you can use the DispatcherTimer class which was built exactly for this kind of situation where you can provide an interval and the DispatcherTimer will call your method periodically at that interval and will do that automatically on the Dispatcher Thread.

How to access control on MainForm from other Thread without coupling?

I have few controls on my MainForm in Winforms application. For example control that updates progress of each operation. These operations are classes which in run in different Thread.
How can i properly update those controls ?
in your main form you can add a function like this one
private delegate void doSomethingWithTheControlsDelegate(object obj);
public void doSomethingWithTheControls(object obj) {
if (this.InvokeRequired) {
this.BeginInvoke(new doSomethingWithTheControlsDelegate(this.doSomethingWithTheControls), obj);
} else {
// do something
}
}
the best way is to do that by events.
the easier way is to change them directly.
ensure that they are public and you overgive them to the class and then you can change it
Invoke(new MethodInvoker(delegate { frmMain.label1.Text = "bla"; }));
I would suggest using a model class which would contain the data which is displayed to the user. Databind your UI controls to the properties of the model, and update the model values from the worker threads (using appropriate invokations to ensure the update occurs on the UI thread so that you don't get the cross thread exception)

Categories

Resources