Cannot get Begin.Invoke, ObservableCollection and background thread to work - c#

This might be an easy/stupid question, but I cannot simply get the three things in the title to work. I am following the advice from the following SO answer:
WPF listbox dynamically populated - how to get it to refresh?
Create an ObservableCollection and set your ListBox.ItemsSource to that collection. Because the collection is observable, the ListBox will update as its contents change.
However, if your real operation is blocking the UI thread, this may
prevent WPF from updating the UI until the operation completes
(because the WPF data binding infrastructure doesn't get a chance to
run). So you may need to run your lengthy operation on a background
thread. In this case, you will not be able to update the
ObservableCollection from the background thread due to WPF
cross-threading restrictions (you can update properties, but not
collections). To get around this, use Dispatcher.BeginInvoke() to
update the collection on the UI thread while continuing your operation
on the background thread.
I created ObservableCollection and attached it to the ItemSource of my ListBox:
private ObservableCollection<Employee> employeeList;
searchList.ItemsSource = employeeList;
I create a thread and instruct it to run the function that populates the ObservableCollection (I am passing text string and ObservableCollection):
Thread thread = new Thread(() => ldapConnector.Search(textbox.Text, employeeList));
thread.Start();
ldapConnector is a separate class that I initialized, when I started the program. And then I get the following error (pointing at thread creation line):
The calling thread cannot access this object because a different
thread owns it.
As I understood from that post, I would need to run Begin.Invoke() method right after thread creation like this:
Dispatcher.BeginInvoke(new Action(()=>{
searchList.ItemsSource = employeeList;
}));
What I am doing wrong and how to fix it?

Related

UWP ProgressRing is shown with big lag during data loading in background

I'm using UWP GridView with DataTemplateSelector to show data for different weeks. When I change the week I want to show loader when data is loading. I'm using MvvmLight for ViewModels binding and when I change the data I'm removing and adding elements to the GridView source. The problem is that when I change IsActive property to true before I run UpdateGrid method, the loader is not active and there is a lag on screen. If data loading (UpdateGrid method) takes more than one sec the loader is visible, so it means for me that the logic there is ok, but the problem can be with generating graphical elements on the screen and performance?
I was trying to make my UpdateGrid method async and sync (there is no api call inside, so can be sync). The method is called in the ViewModel class:
DispatcherHelper.CheckBeginInvokeOnUI(async () =>
{
SyncLoadingImageVisible = true;
await UpdateGrid();
SyncLoadingImageVisible = false;
});
You may be misunderstanding the way async/await works. When you mark a method async ans it contains no real await (meaning no I/O bound operation or operation that actually takes place on another thread), the whole method will essentially run synchronously. This is true in your case as well as you mentioned there is no actual async work inside UpdateGrid so the code will work as if there was really no await.
The UI thread will be busy all the time from the moment you set the SyncLoadingImageVisible to true to the moment you set it back to false - during that time UI thread is 100% dedicated to executing your code so user won't see any UI changes. This causes the behavior you are seeing - that there is a lag as the UI thread does not have a chance to update the UI until the UpdateGrid method finishes executing.
To solve this properly you will have to offload performance intensive, non-UI tasks in UpdateGrid method to another thread using awaited Task.Run and only the code that really does work with the app's UI should then be executed on UI thread. This way you will free the UI thread to be able to display progress to the user while the execution runs in the background.

How can Load my UserControl using different threads?

I'm using .NET C# and trying to load a UserControl that contains three tabs. What I want to do is, to load the first Tab make it visible and responsive and then load the other two tabs in a background thread (or worker or task).
I've tried to do so using a thread, a task, a backgroundworker but always have the same exception "The calling thread cannot access this object because a different thread owns it."
here some code samples I used:
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerAsync();
in the bw_DoWork I put all the work I need to do that involves UI updates
Task t1 = new Task(DoWork);
t1.Start();
in the DoWork I do the same( some code with UI update)
Thanks for the help.
Oussema
This is not impossible, but it is a bad idea - it's very tricky to get right.
Instead, you want to separate the business logic from the UI, so that you can do the logic in the background, while the UI is still on the UI thread. The key is that you must not modify the UI controls from the background thread - instead, you first load whatever data you need, and then either use Invoke or ReportProgress to marshal it back on the UI thread, where you can update the UI.
If creating the controls themselves is taking too long, you might want to look at some options at increasing the performance there. For example, using BeginUpdate and friends to avoid unnecessary redraws and layouting while you're still filling in the data, or creating the controls on demand (as they are about to be visible), rather than all upfront.
A simple example using await (using Windows Forms, but the basic idea works everywhere):
tabSlow.Enabled = false;
try
{
var data = await Database.LoadDataAsync();
dataGrid.DataSource = data;
}
finally
{
tabSlow.Enabled = true;
}
The database operation is done in the background, and when it's done, the UI update is performed back on the UI thread. If you need to do some expensive calculations, rather than I/O, you'd simply use await Task.Run(...) instead - just make sure that the task itself doesn't update any UI; it should just return the data you need to update the UI.
WPF does not allow you to change UI from the background thread. Only ONE SINGLE thread can handle UI thread. Instead, you should calculate your data in the background thread, and then call Application.Current.Dispatcher.Invoke(...) method to update the UI. For example:
Application.Current.Dispatcher.Invoke(() => textBoxTab2.Text = "changing UI from the background thread");

Access UI element from non UI thread

I've got small WPF / MVVM example project with two visual elements (a ComboBox and a simple TextBlock). Both elements are bound to a property of my ViewModel:
Properties MainViewModel.cs
public const string WelcomeTitlePropertyName = "WelcomeTitle";
private string _welcomeTitle = string.Empty;
public string WelcomeTitle
{
get{ return _welcomeTitle;}
set
{
_welcomeTitle = value;
RaisePropertyChanged(WelcomeTitlePropertyName);
}
}
public const string PositionsPropertyName = "Positions";
private ObservableCollection<int> _positions = new ObservableCollection<int>();
public ObservableCollection<int> Positions
{
get{ return _positions; }
set
{
_positions = value;
RaisePropertyChanged(PositionsPropertyName);
}
}
Bindings MainWindow.xaml
<StackPanel>
<TextBlock Text="{Binding WelcomeTitle}"/>
<ComboBox ItemsSource="{Binding Positions}" />
</StackPanel>
Now I change both properties from a non UI thread like this (which is not allowed, as far as I know it):
System.Threading.ThreadPool.QueueUserWorkItem(delegate
{
int i = 0;
while(true)
{
Positions.Add(i); // Solution 1: this throws NotSupportedException
WelcomeTitle = i.ToString(); // Solution 2: this works
i++;
}
}, null);
Question:
Why does solution 1 throw a NotSupportedExpection (not allowed to change collection from non dispatcher thread) while solution 2 works as desired?
Now I change both properties from a non UI thread like this (which is
not allowed, as far as I know it)
In general, changing property values is perfectly fine no matter what thread you are on. Problems and restrictions may come up when changing a property has an "interesting" side effect.
In this case both of the properties being changed produce interesting side effects, and the difference in observed behavior is due to these side effects being handled (from framework code, which you do not get to see directly) in different ways.
Why does solution 1 throw a NotSupportedExpection (not allowed to
change collection from non dispatcher thread) while solution 2 works
as desired?
When a binding source's properties are changed the WPF binding system responds by making the corresponding updates to the UI; however, when the changes are made from a background thread then the binding system's event handler will also run in the background thread and will not be able to update the UI directly. This is true for both the cases in question here.
The difference is that for "simple" property value changes the binding system automatically detects that it's not responding to the change on the UI thread and dispatches the UI changes to the correct thread using Dispatcher.Invoke, but when the observable collection is modified this dispatch does not happen automatically. The result is that the code that updates the UI runs on the background thread and an exception is thrown.
The solution
There are two things either one of which can solve this problem:
Make the property change in the UI thread directly
If the change is made on the UI thread then any PropertyChanged handlers will also run on the UI thread, so they will be free to make any UI changes they want. This solution can be enforced in your own code and will never result in a problem, but if no UI changes are required the extra work of dispatching to the UI thread will have been done for no benefit.
Make sure the PropertyChanged handler dispatches any UI-related changes to the UI thread
This solution has the benefit that it only dispatches work on demand, but also the drawback that the event handler (which might not be your own code) must be explicitly programmed to make the dispatch. .NET already does this for plain properties, but not for ObservableCollection. See How do I update an ObservableCollection via a worker thread? for more information.
The simple Property binding is automatically dispatched to the GUI thread by WPF and can be changed from a non-UI thread. However, this is NOT true for collection changes (ObservableCollection<> BindingList<>). Those changes must happen the UI thread the control was created on.
If I remember correctly, this was not true (solution 2 did not work also) in the early years of WPF and .NET.

Creating heavy user controls in WPF

I know that I cannot spawn different threads and put it in the UI thread to add it in the Visual Tree as it will throw an exception that it cannot access that object because a different thread owns it.
My current scenario is that I am heavily creating UI controls runtime, say like 200 (FrameworkContentElement) controls and add it to the DockWindow. Is it possible for me not to freeze the UI while creating this and try to load them to the UI thread? I cannot even show a progress dialog because that will use the UI thread while showing the dialog while doing work on another thread, that is okay if what I need to handle is data and put it in the UI but this time I need to create these UI controls.
One approach I've thought is create the UI controls and serialize them into MemoryStream and load them to the UI thread, one problem in here is that I have to re-attach the DataContext to the controls but that is fine, at that moment I can delegate it to another thread. Problem still is that is this feasible to do?
I tried mixing Task and Thread object to make the ApartmentState to STA but still no luck.
public static Task<T> StartSTATask<T>(Func<T> func)
{
var tcs = new TaskCompletionSource<T>();
Thread thread = new Thread(() =>
{
try
{
tcs.SetResult(func());
}
catch (Exception e)
{
tcs.SetException(e);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return tcs.Task;
}
EDIT: These controls again are FrameworkContentElement, virtualizing controls in this scenario won't help. This is with FlowDocument controls creating the controls in runtime. Say, Runs, Tables, Paragraphs, etc.. Therefore, ListBox, TreeViews, etc are not applicable in this scenario.
200 controls shouldn't pose that big of a problem to render WPF on a decent machine can take a few thousand primitives.
You can show a progress bar while loading your data and while parsing it. Then you can throttle creating the UI elements if needed by having and off-UI-thread process loop over your data and call UI thread to instantiate controls. You can even separate instantiations by a small sleep to let the screen render, but only use this for VERY heavy UI...
... that being said - if your UI is so heavy you're probably designing it wrong. The question should not be
"how many UI elements can I put before my UI slows down to a drag?"
but
"what's the smallest number of active UI elements that can do the job?".
The word "active" refers to the approach taken by listviews where the actual items are virtualized - they are only created as needed and disposed if not visible. So instead of a DockPanel consider using a virtualizing container, such as a ListView, if your UI allows for it;
I can elaborate further if you can provide an example of your specific UI elements.

C# - Cross-thread operation - Create Control in thread, add to main form

I have an older form that I really don't want to rewrite at this point, so what I'm doing is loading the form and then adding it to a panel in the new UI form. This is working fine, but it's slow. The old form does a lot of loading and gathering of data and it's not very efficient. So as a result larger records take up to 30 seconds to load. As you know, creating the form then "locks up" the main UI for about 30 seconds while it loads the old form. This is the action I'm trying to prevent. I want to load the new form, display a "Loading" gif in the blank panel, and then once the old form is loaded remove the "Loading" image and add the form as a control.
And here starts the problem.
I've tried creating a Background Worker but this causes a STA error (old form has a few threaded data loadings of it's own), and since I can't change the worker to STA I stopped trying.
I've tried to create an Invoke (and BeginInvoke) and while this works, it doesn't really load the old form in the thread. It simply sends it back to the UI thread and does the work there. Again this hangs the UI. I.E.: Not what I want.
I've tried to create a delegate and trigger it as an event in the thread, but I get the same results as below...
I've created a thread, set STA on it, started it and then did a while loop with a DoEvents waiting on it to finish. Of course this all seems to work up to the point of accually adding the form to the panel, and then I get the "Control 'ChartForm' accesses from a thread other than the thread it was created on". In this error 'ChartForm' is the old chart that was loaded in the thread.
I've tried the above method, but I instead used a private static field to hold the creating of the old form, and then adding it to the panel once the thread is completed. This is in the method that created the thread, just after the while loop. Same error.
So, I've used the above method in other places with DataTables and didn't have any issue getting the data back to the main thread for use with DataBinding. I know that this is a little different but I didn't think that it would be this hard to do.
Below is the code that I have tried to use that seems to be the closest to what I want.
private static _ChartForm;
private void LoadPatientChart()
{
ClearMainPanel(); // Removes any loaded ChartForms from Panel
if (_Patient == null) // Test to make sure a patient is loaded
return;
loadingPanel.Visible = true; // Displays the "Loading" gif
Thread thread = new Thread(new ThreadStart(this.GetChartForm));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
while (thread.ThreadState != ThreadState.Stopped)
Application.DoEvents(); // Keeps the UI active and waits for the form to load
this.ChartPanel.Controls.Add(_ChartForm); // This is where the error is
loadingPanel.Visible = false; // Hide the "Loading" gif
}
private void GetChartForm()
{
ChartForm chartForm = new ChartForm(_Patient.AcctNum.ToString(), false);
chartForm.TopLevel = false;
chartForm.FormBorderStyle = FormBorderStyle.None;
chartForm.Dock = DockStyle.Fill;
chartForm.Visible = true;
_ChartForm = chartForm;
}
It's really not a good idea to create UI controls on any other thread than the UI thread. It is technically possible, but it's difficult to manage, especially if the new thread is a "temporary" one.
What you really need to do is refactor out the work that the ChartForm is doing (on construction it appears?) and do that work on a background thread, and then return it to your UI thread and then create your ChartForm passing in the results of that work. IMHO this is a better design anyways; although it may be a lot of work for you.
I don't think what you want is possible without refactoring this "old form". There is only one UI thread, and all UI elements must be created on that thread to be displayed to the user.
I would suggest refactoring the form to display initially without any data (or maybe with a loading image), and then have the form start a background task using BackgroundWorker to perform the long running tasks that are not UI related (going to a database, etc.) Once the worker is complete, then you can run the code that initializes the Form's data elements. This will keep the UI responsive for as long as possible while the blocking tasks are performed.
I've tried to create an Invoke (and BeginInvoke) and while this works,
it doesn't really load the old form in the thread. It simply sends it
back to the UI thread and does the work there. Again this hangs the
UI. I.E.: Not what I want.
You must update the user interface on the main thread, you do not have any choice, if its still hanging then your doing the calculations in the wrong thread.

Categories