WPF NotifyPropertyChange from different thread - c#

I have my VM implemented INotifyPropertyChanged interface. I created another thread T for populating a list that I bind to Xaml. After list is populated, I call PropertyChanged in thread T, and my UI got refreshed correctly.
My question is in what case I would need to use Dispatcher? Why I don't need to use Dispatcher in my case? I thought Dispatcher is used when the code in other thread want to notify the changes to the UI thread by enqueuing the changes to the UI refresh queue, such as adding items to ObservableCollection from another thread, and UI thread will then pull data from the queue.
private List<string> _ListData;
public List<String> ListData
{
get
{
if (_ListData == null)
Initialise( () => ListData = ReturnSlow());
return _ListData;
}
set { _ListData = value; }
}
private List<string> ReturnSlow()
{
List<string> Test = new List<string>();
Test.Add("1");
Test.Add("2");
Thread.Sleep(2000);
return Test;
}
public void Initialise(Action initialiser)
{
Task t = new Task(() =>
{
initialiser();
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ListData"));
});
t.Start();
}

Your app has a main UI thread (usually ManagedThreadId==1). If you want to update the UI from an event that gets pull on some other thread you must use the dispatcher. A useful test here is the Dispatcher.CheckAccess() method that returns true if code is on UI thread and false if on some other thread. A typical call looks something like:
using System.Windows.Threading; // For Dispatcher.
if (Application.Current.Dispatcher.CheckAccess()) {
network_links.Add(new NetworkLinkVM(link, start_node, end_node));
}
else {
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(()=>{
network_links.Add(new NetworkLinkVM(link, start_node, end_node));
}));
}
If you're in the main window you can use:
Dispatcher.BeginInvoke(...
If you're in someother context eg a view model then use:
Application.Current.Dispatcher.BeginInvoke(
Invoke vs BeginInvoke
Use Invoke if you want the current thread to wait until the UI thread has processed the dispatch code or BeginInvoke if you want current thread to continue without waiting for operation to complete on UI thread.
MessageBox, Dispatchers and Invoke/BeginInvoke:
Dispatcher.Invoke will block your thread until the MessageBox is dismissed.
Dispatcher.BeginInvoke will allow your thread code to continue to execute while the UI thread will block on the MessageBox call until its dismissed.
CurrentDispatcher vs Current.Dispatcher!
Be ware of Dispatcher.CurrentDispatcher as my understanding of this is that is will return a Dispatcher for the current thread not the UI thread. Generally are you interested in the dispatcher on the UI thread - Application.Current.Dispatcher always returns this.
Additional note:
If you are finding you are having to check dispatcher CheckAccess often then a useful helper method is:
public void DispatchIfNecessary(Action action) {
if (!Dispatcher.CheckAccess())
Dispatcher.Invoke(action);
else
action.Invoke();
}
Which can be called as:
DispatchIfNecessary(() => {
network_links.Add(new NetworkLinkVM(link, start_node, end_node));
});

Related

Running Action asynchronous on Dispatcher won't finish or freezes UI

I just want to run the Drop action asynchronous to display a busy Dialog while moving large amounts. Because the source collection can only be accessed by the Dispatcher, I need to invoke it.
This way the awaited invoke never finishes / the dialog will never be closed.
Using Invoke instead of InvokeAsync here will cause a NotSupportedException for the same reason mentioned above.
public async void Drop(IDropInfo dropInfo)
{
MainViewModel.Current.ShowBusyDialog();
await Task.Run(() =>
{
// This would crash:
// Dispatcher.CurrentDispatcher.Invoke(() =>
await Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
var data = dropInfo.Data as SomeObject;
var collection = (ObservableCollection<SomeObject>)
((ICollectionView) dropInfo.TargetCollection).SourceCollection;
if (data != null)
{
// Operate with collection
}
else if (dropInfo.Data is IEnumerable<SomeObject>)
{
// Operate with collection
}
});
});
// Never reaches this point
MainViewModel.Current.CloseDialog();
}
And this way the UI just freezes, but finishes after work is done:
public async void Drop(IDropInfo dropInfo)
{
MainViewModel.Current.ShowBusyDialog();
await Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
var data = dropInfo.Data as SomeObject;
var collection = (ObservableCollection<SomeObject>)
((ICollectionView) dropInfo.TargetCollection).SourceCollection;
if (data != null)
{
// Operate with collection
}
else if (dropInfo.Data is IEnumerable<SomeObject
{
// Operate with collection
}
});
MainViewModel.Current.CloseDialog();
}
What am I missing out or how could I get this to work like intended?
EDIT
First thanks to your answers and explanations, very helpful!
I tried this now and the collection is at the end of the method updated, the UI won't freezes and the Dialog displays correctly, but the collection won't get updated in the UI neither in the ViewModel.
When I let it operate directly with the collection (on the UI Thread) it will be updated in both directly.
Btw. the Drop method is in the matching ViewModel, but there I can only acces the binded collection as read only due to validation and checking etc. So I can only add/remove items by custom methods, that would be overkill.
Here in the awaited Task Resharper says: Implicitly captured closure: collection
And at the awaited invoke: Implicitly captured closure: dropInfo
But that should be ok, because the operation doesnt takte that long.
public async void Drop(IDropInfo dropInfo)
{
MainViewModel.Current.ShowBusyDialog();
var collection = (ObservableCollection<SomeObject>)
((ICollectionView) dropInfo.TargetCollection).SourceCollection;
if (collection == null)
return;
// ObservableCollection needed for .Move() extension
var collectionCopy = new ObservableCollection<SomeObject>(collection.ToList());
await Task.Run(() =>
{
var data= dropInfo.Data as SomeObject;
if (data!= null)
{
// operate with collectionCopy (Move item)
}
else if (dropInfo.Data is IEnumerable<SomeObject>)
{
// operate with collectionCopy (Move items)
}
});
var dispatcher = Dispatcher.CurrentDispatcher;
await dispatcher.InvokeAsync(() =>
{
collection = collectionCopy;
// Just tried this but didn't work
RaisePropertyChanged(nameof(collection));
});
// collection is updated at this point
MainViewModel.Current.CloseDialog();
}
UPDATE ---------------------------------------------------------------------
Created and upload a sample to show the issue: Click
What am I missing out or how could I get this to work like intended?
As others have noted, Task.Run will execute work on a background thread, and Dispatcher.InvokeAsync will turn around and execute work on a UI thread. So, your code is not actually doing any real work anywhere other than the UI thread, which is why it's "blocking".
The cleanest solution is to copy all the necessary information out of the UI objects while on the UI thread, do any background work on the thread pool, and finally (if necessary) copy any results back into the UI objects.
public async void Drop(IDropInfo dropInfo)
{
MainViewModel.Current.ShowBusyDialog();
// First, copy the data out of the UI objects.
List<SomeObject> list;
var data = dropInfo.Data as SomeObject;
var collection = (ObservableCollection<SomeObject>)
((ICollectionView) dropInfo.TargetCollection).SourceCollection;
if (collection != null)
{
list = collection.ToList();
}
else if (dropInfo.Data is IEnumerable<SomeObject>)
{
list = ((IEnumerable<SomeObject>)dropInfo.Data).ToList();
}
// Then do the background work.
await Task.Run(() =>
{
// Operate with `list`
});
// Finally, update the UI objects after the work is complete.
MainViewModel.Current.CloseDialog();
}
You're running all this code on the UI dispatcher
await Dispatcher.CurrentDispatcher.InvokeAsync(() =>
What you NEED to do is...
Run all your work in here await Task.Run(() =>
After you're finished, update the UI using the dispatcher.
By the way, if you're updating bound properties that implement INotifyPropertyChanged, you don't even have to use the dispatcher. Update the properties from whatever thread. The Binding will automatically marshall updates onto the UI thread.
The Dispatcher you're using in your code is the UI Dispatcher. That means, when you dispatch a method onto it, you are executing the method on the UI thread. ONLY ever use the Dispatcher at the last possible moment, and only use it to
Update the UI from a different thread
Schedule UI work after application lifecycle events (see the DispatcherPriority enum)
One more problem...
Dispatcher.CurrentDispatcher retrieves the ... current dispatcher. What is the current dispatcher? It's the dispatcher for the current thread. In your first example, the current thread is a background thread. You want to use the dispatcher from the UI. Here's the first example, but tweaked...
public async void Drop(IDropInfo dropInfo)
{
// We are in the UI thread here!
MainViewModel.Current.ShowBusyDialog();
// as we are on the UI thread, no worries touching the collection here
var collection = (ObservableCollection<SomeObject>)
((ICollectionView)dropInfo.TargetCollection).SourceCollection;
// as we are on the UI thread, this is the UI dispatcher.
var dispatcher = Dispatcher.CurrentDispatcher;
await Task.Run(() =>
{
// We are on a background thread here!
var data = dropInfo.Data as SomeObject;
YourResults results = null;
if (data != null)
{
results = WhateverProcessingTakesALongTime();
}
else if (dropInfo.Data is IEnumerable<SomeObject>)
{
results = SameHereIHaveNoIdea(dropInfo.Data);
}
// Now, let's update the UI on the UI thread.
await dispatcher.InvokeAsync(() =>
{
UpdateStuffWithStuff(collection, results);
});
});
MainViewModel.Current.CloseDialog();
}

WPF User Control children not updating c#

I am trying to add a child control (UserControl) to a Grid and the changes are not reflecting. But if another child control (UserControl) is added to the same grid then layout gets updated and both child are visible. This operation perform on button click.
/*this Operation is perform in Backgroud Worker */
void func()
{
/*adding first User Control*/
addRemoveChild(true,FirstChild);//even tried to run this fuc with Dispatcher
FixButton();
addRemoveChild(false,FirstChild);
}
void addRemoveChild(bool isAdd,UserControl uc)
{
if (isAdd)
{
parentGrid.Children.Add(uc); /*parentGrid is Parent Grid*/
parentGrid.UpdateLayout();
return;
}
else
{
parentGrid.Children.Remove(uc);
parentGrid.UpdateLayout();
}
}
void FixButton()
{
/* here some operation is perform which takes 5 min to complete till then FirstChild is not visible*/
addRemoveChild(true,secondChild); /*When this Func run the first Child is visible*/
}
Your function is performed in a background worker: It's not done in the Dispatcher Thread. Every time you use a Dispatcher object (an object that has been created by the Dispatcher thread such as Controls) you should be in the Dispatcher thread.
The background worker is useful to perform task and update UI relative to the task's status in "real time".
You aren't using the background worker correctly. The code in the DoWork is executed in a separate thread whereas the ProgressChanged callback is executed in the Dispatcher thread.
What your code should look like this:
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (sender, args) => {
bw.ReportProgress(0);
FixButton();
bw.ReportProgress(100);
};
bw.ProgressChanged += (sender, args) => {
if (args.ProgressPercentage == 0) {
parentGrid.Children.Add(uc);
} else if(args.ProgressPercentage == 100) {
parentGrid.Children.Remove(uc);
}
};
bw.RunWorkerAsync();
By the way you don't need to call UpdateLayout() and your DoWork callback function should never use Dispatcher objects (remove addRemoveChild from FixButton function)

The calling thread cannot access this object error

I am trying to create dynamically custom userControl in background thread.
This is my method where I am creating new thread:
var thread = new Thread(CreateItemInBackgroundThread);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
And this is method CreateItemInBackgroundThread:
var uc = new MyUserControl();
UserControl item = uc;
AllControls.Add(item);
//Here I am trying to add control to a current Tab
foreach (var currentTab in _allTabs)
{
currentTab.DocumentWindow.Dispatcher.BeginInvoke(new Action(() =>
{
if (tab.DocumentWindow.IsSelected)
{
tempTab = tab;
tempControl = item;
finish = true;
}
}));
}
This is my finish property
bool finish
{
get { return _finish; }
set
{
_finish = value;
if (_finish)
{
tempTab.AnimatedCanvas.Dispatcher.BeginInvoke(new Action(() => tempTab.AnimatedCanvas.Children.Add(tempControl)));
}
} // Here I get error - The calling thread cannot access this object because a different thread owns it
}
How can I avoid this error and why this error happend?
as the error says, you can't access the this object because a different thread owns it, so you can invoke that thread using Invoke(Delegate Method)
you can check if invoke required using tempTab.InvokeRequired
This error is coming because u must be doing the different tasks on the same thread like U cannot make a thread go async and update the UI using the same thread.It will cause conflict.because UI thread is the main thread.
You can use BAckground Worker Thread and subsribe its two eventHandlers to your events which you want to work on.. for eg-
BackgroundWorker Worker=new BackgroundWorker();
worker.DoWork+=Yorevent which will do the timeTaking Task();
Worker.RunWorkerCompleted+=YOurEvent which will Update your UI after the work is done();
worker.RunWorkerAsync();
RunWorkerAsync() will make your thread go async and work on background
this way it will not cause any thread Error too..

Thread synchronization in WinCE

I have faced with a problem of threads synchronization. My presenter analyzes some sensors and update UI form. I moved updating code into separate thread. It works fine, but if the user stops presenter when it is updating the view, the software freezes - I found that it happens when view.UpdateUI working (it just set some labels using Invoke). Where my problem is? I use compact framework 3.5 and Windows CE 5
using System.Threading;
class MyPresenter
{
UserControl view;
private Thread thread;
private ManualResetEvent cancelEvent;
public void Start()
{
cancelEvent = new ManualResetEvent(false);
thread = new Thread(UpdateView) { IsBackground = true };
thread.Start();
}
public void Stop()
{
if (thread != null) {
cancelEvent.Set();
thread.Join();
thread = null;
}
}
private void UpdateView()
{
while (cancelEvent.WaitOne(1000, false) == false) {
// analyze something
view.UpdateUI(...);
}
}
}
Don't update the UI thread directly from within a worker thread. Use a delegate instead.
For example: How to update the GUI from another thread in C#?
If your background thread is blocked calling your UI (via Control.Invoke), and then your UI thread is blocked calling your Stop method with its thread.Join() you've got yourself a classic fatal embrace. You should get rid of the Join and instead have the background thread raise one last event / notification when the Stop completes so the UI can deal with that (enable/disable buttons etc).

Making a Controls.Add method call thread-safe

Let's say I have the following code:
public void Inject(Form subform)
{
this.tabControl1.TabPages[1].Controls.Add(subform);
this.Refresh();
}
How can I convert the Controls.Add() call to a thread-safe call, using Control.Invoke?
The only way to make Control.Add thread safe is to make sure it's called from the UI thread. This also implies that the Control being added is usable from the UI thread.
Here is a function though which produces a delegate which can add to a Control from any thread (presuming the added Control is OK on the UI thread).
public Action<Control> GetAddControl(this Control c)
{
var context = SynchronizationContext.Current;
return (control) =>
{
context.Send(_ => c.Controls.Add(control), null);
};
}
Then for a given Control you can pass the resulting delegate to any thread.
// From UI thread
Action<Control> addControl = c.GetAddControl();
// From background thread
addControl(subForm);

Categories

Resources