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

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();
}

Related

My datasource isn't updating when changing the binding list using a task

I want to bind my combobox's data source using a binding list. The binding list contains a collection of Client instances. I managed this and it works well, but because reading from the database takes long I decided to use a task. And now once the binding list is updated via the task the combobox still has no values.
I wanted to use a normal thread at first, but struggled so switched to using a task(pretty much the same thing I guess thing I guess). So a solution using threads would be just as useful.
public partial class frmJobCreation : Form
{
public frmJobCreation()
{
InitializeComponent();
}
BindingList<Client> clients = new BindingList<Client>();
private void frmJobCreation_Load(object sender, EventArgs e)
{
cbxRtojClient.DataSource = clients;
Task.Run(() =>
{
clients = new BindingList<Client>(Client.GetClients());
});
}
}
Where Client.GetClients() is a static method that returns List<Client>
In the form load event you have cbxRtojClient.DataSource = clients; and then you are updating the clients object in the Task. But this is never going to update the ComboBox.
A Task in C# (wrt winforms) should ideally perform a task and return its results so that it can be updated in the UI. The UI update should ideally be done in a ContinueWith continuation task which is run in the UI context. We should always update any control in the context of the UI thread. Check this for reference.
Task.Run<BindingList<Client>>(() =>
{
// return all the clients to 'ContinueWith' task
return new BindingList<Client>(Client.GetClients());
})
.ContinueWith(t =>
{
// Result is a dynamic property and holds the output of its previous Task. BindingList<Client> in this case.
clients = t.Result;
// Update UI
cbxRtojClient.DataSource = clients;
}, TaskScheduler.FromCurrentSynchronizationContext()); // ensures calling the ContinueWith task to be run in the UI thread.
To know more about Task Parallel Library (TPL) follow this.

WPF NotifyPropertyChange from different thread

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));
});

How to run codes on background thread in Windows Runtime

I'm using incremental loading to show a ListView items. I run LoadDetails method in the background thread using Task.Run(...) to not busy the UI thread.
But it still blocks the UI thread and it doesn't render UI elements until it finishes the task.
executing LoadDetails method takes around 3 seconds to complete.
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
await Task.Run(() => LoadDetails(textBlock, item.Id));
}
private async Task LoadDetails(TextBlock textBlock, string id)
{
int count = await DataSource.GetItemCounts(id);
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
textBlock.Text = count.ToString();
});
}
How to fix this so it doesn't block the UI thread? thanks.
(It's a Windows Phone Runtime app)
It's not clear from your question how you are measuring the 3 second delay. Is it that the call to GetItemCounts() itself takes 3 seconds? If so, isn't that to be expected? The delay is why you would execute that asynchronously in the first place, isn't it?
The code you posted doesn't really seem quite right. Since your new Task doesn't await the call to LoadDetails(), that task will finish right away, without any synchronization with the actual work being done. Written differently, you could also avoid having to call through the Dispatcher directly.
I would have written it something more like this:
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
await LoadDetails(textBlock, item.Id);
}
private async Task LoadDetails(TextBlock textBlock, string id)
{
int count = await DataSource.GetItemCounts(id);
textBlock.Text = count.ToString();
}
I.e. as long as you keep awaiting on the UI thread, you don't need to invoke via the Dispatcher. Note that the above assumes you need the LoadDetails() method, presumably because you call it from multiple places and some require this particular implementation for some reason. But note that you could have just written the LoadItemCounts() method like this, and left out the LoadDetails() method altogether:
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
textBlock.Text = (await DataSource.GetItemCounts(id)).ToString();
}
It looks like your code is correctly not blocking the UI thread by using await, but since LoadItemDetails() is presumably being called on the UI thread, it won't finish until the method is finished doing its work.
To fix this, just omit the await on the call to Task.Run(), so something like
Task.Run(() => LoadDetails(textBlock, item.Id));
should make LoadItemDetails() return immediately.

Update ObservableCollection in list box in thread

Hy,
I have a Observable Collection which is bind with a list box. I add logs to the Observable Collection. I always add the message immediately to the Observable Collecten. But the list gets only updated when the loop is finished but I want to Update it when I add one item in the for loop. This is why I use a Thread but I have a few problems.
I have a thread safe ObservableCollection:
class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler collectionChanged = this.CollectionChanged;
if (collectionChanged != null)
foreach (NotifyCollectionChangedEventHandler handler in collectionChanged.GetInvocationList())
{
DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
if (dispatcherObject != null)
{
Dispatcher dispatcher = dispatcherObject.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => handler.Invoke(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
}
handler.Invoke(this, e);
}
}
}
This is my test class:
public partial class MainWindow : Window
{
ThreadSafeObservableCollection<Animal> list = new ThreadSafeObservableCollection<Animal>();
public MainWindow()
{
InitializeComponent();
list.Add(new Animal() { Name = "test1" });
list.Add(new Animal() { Name = "test2" });
this.DataContext = list;
}
private void dsofsdkfd(object sender, RoutedEventArgs e)
{
//Version 1
Task.Factory.StartNew(() => test());
//Version2
/*
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var token = Task.Factory.CancellationToken;
Task.Factory.StartNew(() => test(), token, TaskCreationOptions.None, uiScheduler);
*/
}
public void test()
{
for (int i = 0; i < 10000; i++)
{
list.Add(new Animal() { Name = "test" + i });
System.Threading.Thread.Sleep(1);
}
}
}
See the private void dsofsdkfd(object sender, RoutedEventArgs e) function to the comment Version1.
In the beginning it works so the list updates everytime I add a item. After a few entries I get an exception:
"Information for developers (use Text Visualizer to read
this):\r\nThis exception was thrown because the generator for control
'System.Windows.Controls.ListBox Items.Count:1089' with name 'Logger'
has received sequence of CollectionChanged events that do not agree
with the current state of the Items collection. The following
differences were detected:\r\n Accumulated count 994 is different
from actual count 1089. [Accumulated count is (Count at last Reset +
Adds - #Removes since last Reset).]\r\n\r\nOne or more of the following sources may have raised the wrong events:\r\n
System.Windows.Controls.ItemContainerGenerator\r\n
System.Windows.Controls.ItemCollection\r\n
System.Windows.Data.ListCollectionView\r\n *
WpfApplication1.ThreadSafeObservableCollection`1[[WpfApplication1.Animal,
WpfApplication1, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null]]\r\n(The starred sources are considered more
likely to be the cause of the problem.)\r\n\r\nThe most common causes
are (a) changing the collection or its Count without raising a
corresponding event, and (b) raising an event with an incorrect index
or item parameter.\r\n\r\nThe exception's stack trace describes how
the inconsistencies were detected, not how they occurred. To get a
more timely exception, set the attached property
'PresentationTraceSources.TraceLevel' on the generator to value 'High'
and rerun the scenario. One way to do this is to run a command
similar to the following:\n
System.Diagnostics.PresentationTraceSources.SetTraceLevel(myItemsControl.ItemContainerGenerator,
System.Diagnostics.PresentationTraceLevel.High)\r\nfrom the Immediate
window. This causes the detection logic to run after every
CollectionChanged event, so it will slow down the application.\r\n"
See private void dsofsdkfd(object sender, RoutedEventArgs e) function to the comment Version2.
I also tried it with the TaskScheduler using FromCurrentSynchronizationContext.
Then it throws no exception but I have the same problem like at the beginning, so the list box refreshes only if the for each loop is finished.
How I can accomplish that the list box updates when I add an element?
Best regards
I wouldn't roll my own ObservableCollection for this. I'd just perform the .Add call on the UI thread.
public void test()
{
for (var i = 0; i < 10000; i++)
{
// create object
var animal = new Animal {Name = "test" + i};
// invoke list.Add on the UI thread
this.Dispatcher.Invoke(new Action(() => list.Add(animal)));
// sleep
System.Threading.Thread.Sleep(1);
}
}
Note that since you're in a Window subclass, this.Dispatcher will correspond to the dispatcher for the UI thread. If you move this logic to, say, a model or view model class, you'll need to explicitly capture the value of Dispatcher.Current on the UI thread, and pass that dispatcher manually to the background thread.
EDIT: OP asked for more information on using the Dispatcher outside of a FrameworkElement class. Here's how you would do that. The dispatcher for the UI thread is acquired on the UI thread by calling Dispatcher.CurrentDispatcher. That dispatcher is then passed directly into the background thread procedure.
public class MainWindowViewModel
{
// this should be called on the UI thread
public void Start()
{
// get the dispatcher for the UI thread
var uiDispatcher = Dispatcher.CurrentDispatcher;
// start the background thread and pass it the UI thread dispatcher
Task.Factory.StartNew(() => BackgroundThreadProc(uiDispatcher));
}
// this is called on the background thread
public void BackgroundThreadProc(Dispatcher uiDispatcher)
{
for (var i = 0; i < 10000; i++)
{
// create object
var animal = new Animal { Name = "test" + i };
// invoke list.Add on the UI thread
uiDispatcher.Invoke(new Action(() => list.Add(animal)));
// sleep
System.Threading.Thread.Sleep(1);
}
}
}
You need to maintain current dispatcher thread for the same. You must update collection in current dispatcher thread only. One way to do it is to use BiginInvoke() method of dispatcher class.
Save current dispatcher in a variable in constructor and then use it when needed.
_currentDispatcher = Application.Current.Dispatcher;
For example: We have a scenario where we popup an error window if we have an error. We need to close an Error window if error count is zero. Now if we are handling events and message in another thread (not on UI thread) then we need to save the UI thread dispatcher object and need to use it to update collection or any other action. Here I am closing Error Window. (I don't have solution ready for updating collection.)
if (ErrorNotifications.Count == 0)
_currentDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<ErrorNotificationWindow>(CloseErrorNotificationWindow), _errWindow);
Here CloseErrorNotificationWindow is method with parameter _errWindow.
private void CloseErrorNotificationWindow(ErrorNotificationWindow _errWindow)
{
if (_errWindow == null)
return;
if (_errWindow.IsActive)
_errWindow.Close();
}
In CloseErrorNotificationWindow() method you can update your collections and it should not give any exception as you would be using main UI thread to do it.
Hope this will helpful.

BlockingCollection + UI Thread

I've followed this tutorial, to create a priority queue and wrapped it with a blocking collection. I've got a DataGrid which I've wired to the underlying priority queue which emits change events. I can add items to the collection from the UI thread w/out a hitch, and it blocks when the buffer is full as it's supposed to.
Now how do I consume the items? Here's what I've got:
public DownloadViewModel()
{
Queue = new ConcurrentPriorityQueue<DownloadItem>(10);
Buffer = new BlockingCollection<KeyValuePair<int, DownloadItem>>(Queue, 10000);
Task.Factory.StartNew(() =>
{
KeyValuePair<int, DownloadItem> item;
while(!Buffer.IsCompleted)
{
if(Buffer.TryTake(out item))
{
// do something with the item
}
Thread.SpinWait(100000);
}
});
}
But as soon as I added that Task.Factory.StartNew bit, my app suddenly takes 30 seconds before the window appears (before it was instant), and when I do add an item I get the exception
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
Which I understand, but is it really necessary to take the items using the UI thread? Doesn't that defeat the whole purpose of using this BlockingCollection? I want to create 4 or 8 consumers and have them run in parallel.
How is this supposed to be done?
Wrapping the CollectionChanged event w/ a dispatcher seems work pretty well...
public bool TryAdd(KeyValuePair<int, T> item)
{
int pos = _queues.Take(item.Key + 1).Sum(q => q.Count);
_queues[item.Key].Enqueue(item.Value);
Interlocked.Increment(ref _count);
Dispatcher.BeginInvoke(
new Action(
() =>
NotifyCollectionChanged(
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, pos))
));
return true;
}
Just had to derive my ConcurrentPriorityQueue from DispatcherObject. I think this is how it's supposed to be done.
Easier yet, just write the NotifyCollectionChanged method like this:
private void NotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
{
lock (CollectionChanged)
{
if (CollectionChanged != null)
Dispatcher.BeginInvoke(new Action(() => CollectionChanged(this, e)));
}
}
And then you don't have to litter your other methods with BeginInvoke.
[After commenting on the question, then]
You don't need to "take the items using the UI thread". However, any updates to the UI as a result of processing the item in the consuming task need to be dispatched to the UI thread. Separate your concerns!

Categories

Resources