Adding UserControls to Grid Asynchronously [duplicate] - c#

This question already has answers here:
Dispatcher and async await in WPF
(2 answers)
Closed 1 year ago.
I am working with a WPF application and I am facing problems while applying Navigation, the screen freezes , So I want to achieve Asynchronicity
My method of navigation : I create a grid and add User controls to the children property of that grid
and since I have so many UI elements on numerous different User controls it freezes the Application
I want to add A user control asynchronous on when the window is loaded, My idea is to use the async await keywords but obviously I am using them incorrectly, I have researched and do not understand why it is suggested to use dispatcher even after there being async await so I wanted to follow that way (async/await)
This is just a sample problem of the real deal
this is the code
private async void grid1_Loaded(object sender, RoutedEventArgs e)
{
txtb1.Text = "";
var watch = System.Diagnostics.Stopwatch.StartNew();
await gy();
watch.Stop();
var elapsedtm = watch.ElapsedMilliseconds;
txtb1.Text += $"TOTAL TIME {elapsedtm} \n\n\n";
}
private async Task gy()
{
////////////
Y1 child1 = new Y1();
await Task.Run(() => grid1.Children.Add(child1));
///////////
}

private async void grid1_Loaded(object sender, RoutedEventArgs e)
{
txtb1.Text = "";
var watch = System.Diagnostics.Stopwatch.StartNew();
//Asynchronous execution of the "gy" method in the UI thread.
await Dispatcher.InvokeAsync(gy);
watch.Stop();
var elapsedtm = watch.ElapsedMilliseconds;
txtb1.Text += $"TOTAL TIME {elapsedtm} \n\n\n";
}
// This method can only be called on the main UI thread.
private void gy()
{
////////////
UIElement child1 = new Y1();
grid1.Children.Add(child1);
///////////
}
If in the gy method there are some long-term operations without using UI elements and you need to free the main UI thread from their execution, then this option:
private async void grid1_Loaded(object sender, RoutedEventArgs e)
{
txtb1.Text = "";
var watch = System.Diagnostics.Stopwatch.StartNew();
await gy();
watch.Stop();
var elapsedtm = watch.ElapsedMilliseconds;
txtb1.Text += $"TOTAL TIME {elapsedtm} \n\n\n";
}
private async Task gy()
{
// Here's some lengthy code, but in which there are no calls to UI elements.
////////////
await grid1.Dispatcher.BeginInvoke(new Action(() =>
{
UIElement child1 = new Label() { Content = "Hello" };
grid1.Children.Add(child1);
}));
///////////
// Here's some lengthy code, but in which there are no calls to UI elements.
}

Related

How can I stop the Task.Run()?

I'm newer to the concept of threading and I would like to use Task that is a component of Thread in my application because the save task takes time for executing.
This is my code:
private void SaveItem(object sender, RoutedEventArgs e)
{
// Button Save Click ( Save to the database )
Task.Run(() =>
{
var itemsS = Gridview.Items;
Dispatcher.Invoke(() =>
{
foreach (ItemsModel item in itemsS)
{
PleaseWaittxt.Visibility = Visibility.Visible;
bool testAdd = new Controller().AddItem(item);
if (testAdd)
Console.WriteLine("Add true to Items ");
else
{
MessageBox.Show("Add failed");
return;
}
}
PleaseWaittxt.Visibility = Visibility.Hidden;
});
});
MessageBox.Show("Save Done");
// update the gridView
var results = new Controller().GetAllItems();
Gridview.ItemsSource = null;
Gridview.ItemsSource = results;
Gridview.Items.Refresh();
}
The problem is that when I save all items, I got duplicate data in the database. Otherwise, the count of ItemsS is fixed to 300, but after the saving, I got 600,
Did Task.Run() repeat the save task to the database ?
NB: I'm working on UI project ( WPF Desktop app )
I'm thinking you'd need something along the lines of this.
I quickly whipped it up but i hope its enough to attempt a fix yourself.
private async void SaveItem(object sender, RoutedEventArgs e)
{
try {
var itemsS = GridviewServices.Items.ToList(); // to list makes shallow copy
await Task.Run(() => {
foreach (ItemsModel item in itemsS)
{
bool testAdd = new Controller().AddItem(item);
}
});
// Dont update ui in task.run, because only the ui thread may access UI items
// Do so here - after the await. (or use dispatcher.invoke).
GridviewServices.Items.Clear();
GridviewServices.Items = itemsS;
} catch { ... } // Handle exceptions, log them or something. Dont throw in async void!
}
I'm also thinking this would work:
private async void SaveItem(object sender, RoutedEventArgs e)
{
// Button Save Click ( Save to the database )
var itemsS = GridviewServices.Items;
await Task.Run(() =>
{
foreach (ItemsModel item in itemsS)
{
Dispatcher.Invoke(() => {PleaseWaittxt.Visibility = Visibility.Visible;})
bool testAdd = new Controller().AddItem(item);
if (testAdd)
Console.WriteLine("Add true to Items ");
else
{
MessageBox.Show("Add failed");
return;
}
}
Dispatcher.Invoke(() => {PleaseWaittxt.Visibility = Visibility.Hidden;})
});
MessageBox.Show("Save Done");
// update the gridView
var results = new Controller().GetAllItems();
Gridview.ItemsSource = null;
Gridview.ItemsSource = results;
Gridview.Items.Refresh();
}
The problem you're running in to, is because the Task you're executing isn't running in parallel, but synchronously to the rest of your application.
When you're running CPU-intensive tasks in the background of your UI-application, you'll want to either work with actual threads or async/await - which is what you attempted with your code.
What you'll want to do is something similar to this:
private async void SaveItem(object sender, RoutedEventArgs e) => await Task.Run(
/*optionally make this async too*/() => {
// Execute your CPU-intensive task here
Dispatcher.Invoke(() => {
// Handle your UI updates here
});
});
This is just a general overview, I don't know your exact use-case, but this should get you started in the right direction.
One thing to be weary of when using Lambdas and such, is closures.
If your application tends to use a lot of memory, you might want to re-think the structure of your calltree and minimize closures in your running application.

Parallel Tasks In WPF

I'm trying to implement parallel tasks in WPF and it seems like it's not working. I have a very simple app that has one button, one label and it performs the following:
The user clicks the button.
The label is updated to show that the app is working.
Two worker methods are run in parallel. Each worker method accesses a UI element as part of it's algorithm.
When the two worker methods are done, the label is updated to show the amount of time it took each to run as well as the overall time of the app to run.
When I run the program, I get the following results:
Worker 1 Time: 00:00:00.2370431
Worker 2 Time: 00:00:00.5505538
Total Time: 00:00:00.8334426
This leads me to believe that the worker methods are probably running in parallel, but something is blocking the application in a synchronous manner. I'm thinking that the total time should be close to the longest running worker method time if they are truly running in parallel. One gotcha I ran into was that because I'm accessing UI elements in the worker methods, I needed to use the Dispatcher.Invoke() method.
Note: I read the documentation on Task-based Asynchronous Programming. It states, "For greater control over task execution or to return a value from the task, you have to work with Task objects more explicitly." That is why I'm running tasks implicitly; I am not returning anything from the worker methods and I don't need greater control (at least I think I don't).
Am I correct in assuming that the app is running the worker methods in parallel and something is forcing the app to behave in a synchronous manner? Why am I getting the sum of the worker methods' times for the total time?
XAML
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow"
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize"
Height="150"
Width="300">
<StackPanel>
<Button x:Name="Button1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="Click Me"
Click="Button1_Click" />
<Label x:Name="Label1"
Content=" " />
</StackPanel>
</Window>
C#
namespace WpfApp1
{
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
public partial class MainWindow
{
private string _t1;
private string _t2;
private Stopwatch _sw;
public MainWindow()
{
InitializeComponent();
}
private async void Button1_Click(object sender, RoutedEventArgs e)
{
_sw = new Stopwatch();
_sw.Start();
Label1.Content = "Working...";
await Task.Run(() =>
{
Parallel.Invoke(Work1, Work2);
Dispatcher.Invoke(UpdateLabel);
});
}
private void UpdateLabel()
{
_sw.Stop();
Label1.Content = $"T1: {_t1}\nT2: {_t2}\nTotal: {_sw.Elapsed}";
}
private void Work1()
{
Dispatcher.Invoke(() =>
{
var text = Button1.Content;
var stopWatch = new Stopwatch();
stopWatch.Start();
for (var i = 0; i < 100000000; i++) { }
_t1 = stopWatch.Elapsed.ToString();
}, DispatcherPriority.ContextIdle);
}
private void Work2()
{
Dispatcher.Invoke(() =>
{
var text = Button1.Content;
var stopWatch = new Stopwatch();
stopWatch.Start();
for (var i = 0; i < 250000000; i++) { }
_t2 = stopWatch.Elapsed.ToString();
}, DispatcherPriority.ContextIdle);
}
}
}
As everyone already stated, you do your "background" work in the UI thread, as you're using the Dispatcher object in a wrong way. You should use it only for UI events. So, a couple of things you can do better in your code:
As you have an async event handler, you can use the UI-oriented code there without the Dispatcher, just move it out from await construct:
private async void Button1_Click(object sender, RoutedEventArgs e)
{
_sw = new Stopwatch();
_sw.Start();
Label1.Content = "Working...";
// save the context and return here after task is done
await Task.Run(() =>
{
Parallel.Invoke(Work1, Work2);
});
// back on UI-context here
UpdateLabel();
}
Your "background" code uses the Button1.Content property, which you can get in your event handler without the Dispatcher, again. After that you may create a lambdas with passing your param to the background actions:
private async void Button1_Click(object sender, RoutedEventArgs e)
{
_sw = new Stopwatch();
_sw.Start();
Label1.Content = "Working...";
var buttonContent = ((Button) sender).Content
// this will work too
// consider to rename your button
// var buttonContent = Button1.Content
// save the context and return here after task is done
await Task.Run(() =>
{
Parallel.Invoke(() => Work1(buttonContent),
() => Work2(buttonContent));
});
// back on UI-context here
UpdateLabel();
}
Now you can remove the Dispatcher again, this time you can do that in your Work# methods (as you do not need to get anything from UI for now):
private void Work1(string text)
{
var stopWatch = new Stopwatch();
stopWatch.Start();
for (var i = 0; i < 100000000; i++)
{
// real work here
}
_t1 = stopWatch.Elapsed.ToString();
}
private void Work2(string text)
{
var stopWatch = new Stopwatch();
stopWatch.Start();
for (var i = 0; i < 100000000; i++)
{
// real work here
}
_t2 = stopWatch.Elapsed.ToString();
}
Finally, you should remove the additional task creation. As Parallel.Invoke doesn't return a Task, so it can't be awaited, you should replace it with a Task.WhenAll method, like this:
private async void Button1_Click(object sender, RoutedEventArgs e)
{
_sw = new Stopwatch();
_sw.Start();
Label1.Content = "Working...";
var buttonContent = ((Button) sender).Content
var w1 = Task.Run(() => Work1(buttonContent));
var w2 = Task.Run(() => Work2(buttonContent));
await Task.WhenAll(w1, w2);
// back on UI-context here
UpdateLabel();
}
Dispatcher in its essence is marshalling work to certain thread. That thread in your context most likely will be UI thread.
So what your code is doing:
Queues Work1 and Work2 to be performed on available threadpool threads.
In Work1 and Work2 you marshall work to UI thread.
Result - all computation heavy work will be performed on a single thread. Which is UI thread.
Am I correct in assuming that the app is running the worker methods in parallel and something is forcing the app to behave in a synchronous manner?
Yes, worker methods are being ran on different threads. Also, you are correct that "something is forcing the app to behave in a synchronous manner" which is Dispatcher.Invoke it marshalls work to UI thread. This means that all computation heavy work being passed as a delegate will be executed on a single thread (UI thread). While Worker methods are being invoked from different thread work is being done on UI thread.
Why am I getting the sum of the worker methods' times for the total time?
Because worker method body is being executed on a single thread.

update text on a WPF page with delays

I new to WPF, and have to put a basic application together
It consists of one main window with a frame, and one page
the page has a basic status text -
the requirement is that when the page loads up, the application has to do a bunch of REST call to fetch some data from remote source, and update the status text as it fetches
problem is, as I update the text, it doesn't seem to be reflected on the page, or maybe it's being blocked - even though I've used Task
so far, I have the following code for testing:
private void Page_Loaded(object sender, RoutedEventArgs e) {
var wnd = Window.GetWindow(this);
wnd.ContentRendered += Wnd_ContentRendered;
}
private void Wnd_ContentRendered(object sender, EventArgs e) {
DisplayMessages();
}
private void DisplayMessages() {
authenticationText.Text = "text one";
var t = Task.Delay(5000);
t.Wait();
authenticationText.Text = "text two";
t = Task.Delay(5000);
t.Wait();
authenticationText.Text = "text three";
t = Task.Delay(5000);
t.Wait();
}
even though I'm waiting after each task, the UI doesn't get updated - rather it just displays text three directly after method is finished - suggestions ?
P.S: there's also a WPF loader on that page, I've noticed that it doesn't get animated as well - it seems the delay is working but everything on the UI isn't updated
I would suggest for getting the data from REST implementation , you should use the background worker and on the basis of completion of thread or progress changed you need to update the UI thread accordingly.
for getting the better insights on background worker.. kindly use this link
How to use WPF Background Worker
In your case you can use progresschanged event of the backgroundworker..
Please Create some property lets say StatusText with InotifyPropertyChanged Interface implemented and bind (use TwoWay Binding) it with the Text property of the authenticationText control .... and in the progress changed event of the backgroundworker set the value of the StatusText property,., which will automatically updates the UI.
You could try to invoke these results on the UI Thread...
Run your task normally with Task.Run or whatever. Each time you are ready to set some property on UI Thread you should invoke it through the dispatcher..
Task.Run(() =>
{
var _Temp = getSomePropTask();
Thread.Sleep(1000);
App.Current.Dispatcher.Invoke(()=>{
authenticationText.Text = _Temp;
});
});
Thanks to suggestion by Ashok, I did some background reading and have come up with the following solution using Task, async and await - which is simpler to manage than background worker threads:
private void Page_Loaded(object sender, RoutedEventArgs e) {
var wnd = Window.GetWindow(this);
wnd.ContentRendered += Wnd_ContentRendered;
}
private void Wnd_ContentRendered(object sender, EventArgs e) {
GetDataAsync();
}
private async void GetDataAsync() {
authenticationText.Text = "Connecting...";
await Task.Delay(5000);
authenticationText.Text = "Getting Member Details...";
List<MemberServiceModel> memberList = await GetMembersAsync();
// more code for handling response
}
private List<MemberServiceModel> GetMembers() {
//get all members synchronous
var request = new RestRequest("Members/Admin", Method.GET);
var response = _client.Execute<List<MemberServiceModel>>(request);
if (response.ResponseStatus != ResponseStatus.Completed) {
//TODO
_restErrorStatus = response.ResponseStatus.ToString();
_restErrorMessage = response.StatusDescription;
_logger.Error("Error in GetMembers");
_logger.Error("Status:" + _restErrorStatus);
_logger.Error("Description:" + _restErrorMessage);
}
return response.Data; ;
}
private Task<List<MemberServiceModel>> GetMembersAsync() {
//get all members asynchronous
return Task.Run(new Func<List<MemberServiceModel>>(GetMembers));
}

UI is frozen while running Task

May be I am wrong but my assuption is that any background thread can read and write into List or ObservableCollection if I don't care about any particular order. If I need a surtain order I will use BlockingCollection.
private void buttonTesting_Click(object sender, RoutedEventArgs e)
{
PrepareDataForTesting();
Stopwatch timer1 = new Stopwatch();
timer1.Start();
//some code preparing data
List<Task> tasks = new List<Task>();
//Testing for each pair
foreach (InterfaceWithClassName aCompound in Group1)
{
foreach (InterfaceWithClassName bCompound in Group2)
{
InstancePair pair = new InstancePair();
//some code
Task task = Task.Factory.StartNew(() => TestPairSerial(pair));
tasks.Add(task);
}
}
var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.ContinueWhenAll(tasks.ToArray(),
antecedents =>
{
timer1.Stop();
TimeSpan ts1 = timer1.Elapsed;
string elapsedTime1 = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts1.Hours, ts1.Minutes, ts1.Seconds, ts1.Milliseconds / 10);
statusLabel_1.Content = "Elapsed time to run the test" + elapsedTime1;
statusLabel_0.Content = "Testing made " + passes + " passes";
pairsResultsDataGrid.ItemsSource = pairsResultsTable.DefaultView;
System.Media.SystemSounds.Exclamation.Play();
}, CancellationToken.None, TaskContinuationOptions.None, ui);
System.Media.SystemSounds.Beep.Play();
}
(Note: I am not sure if it matters - "pair" is found through Reflection)
When I click the button I can hear the last line - System.Media.SystemSounds.Beep.Play(); meaning we out of the event handler and all the threads are launched. But then my application is still frozen untill ContinueWhenAll is done.
TestPairSerial(pair) method has the following structure:
private void TestPairSerial(object instances)
{
do
{
do
{
//here are two methods that read data from minData ObservableCollection
//minData is a public static property of MainWindow
//minData is bound to Chart control (in XAML)
} while (isSetbCompoundParams);
} while (isSetaCompoundParams);
//filling up results into one table and two dictionaries (main window variables)
}
You are executing the tasks in the main thread. You can execute the whole code asynchronously with Dispatcher.BeginInvoke
this.Dispatcher.BeginInvoke(new Action(() => {
// your code here
}), null);

Parallel Programming: Can't access UI Parallel?

I'm trying to create parallel execution of a function in wpf c# which also runs actions on the UI. But when running there is always an exception at methods on UI Controls: The calling thread cannot access this object because a different thread owns it. The exception is always called on the second instance of the loop being run, so it isn't possible to manipulate the UI in two parallel running instances?
Is it possible to acces the UI in parallel?
Code:
do
{
if (listBox_Copy.SelectedIndex < listBox_Copy.Items.Count - 1)
{
listBox_Copy.SelectedIndex = listBox_Copy.SelectedIndex + 1;
listBox_Copy.ScrollIntoView(listBox_Copy.SelectedItem);
}
listBox_Copy.Focus();
huidigitem = listBox_Copy.SelectedItem as ListBoxItem;
currentitemindex = listBox_Copy.SelectedIndex;
currentitem = listBox_Copy.ItemContainerGenerator.ContainerFromIndex(currentitemindex) as ListBoxItem;
itemgrid = FindVisualChild<Grid>(currentitem);
senderbutton = (Button)sender;
Button playcues = itemgrid.FindName("Playbutton") as Button;
cuetrigger = itemgrid.FindName("cuetrigger") as TextBlock;
Jobs.Add(playcues);
} while (cuetrigger.Text != "go");
Parallel.ForEach(Jobs, playcues => { play(playcues, new RoutedEventArgs()); });
And then it crashes at the play function
private void play(object sender, System.Windows.RoutedEventArgs e)
{
Grid itemgrid = VisualTreeHelperExtensions.FindAncestor<Grid>(playcue);
...
}
It is not possible to access the UI from a background thread, all your updates must be on the main thread. You can do this by using the Dispatcher
Something like this
Action x = (Action)delegate {
//do my UI updating
};
Dispatcher.Invoke(x, new object[] { });
The trick is to use an IProgress<T> to report updates to the main thread. The IProgress<T> constructor accepts an anonymous function that will be run in the main thread and can thus update the UI.
Quoting from https://blog.stephencleary.com/2012/02/reporting-progress-from-async-tasks.html :
public async void StartProcessingButton_Click(object sender, EventArgs e)
{
// The Progress<T> constructor captures our UI context,
// so the lambda will be run on the UI thread.
var progress = new Progress<int>(percent =>
{
textBox1.Text = percent + "%";
});
// DoProcessing is run on the thread pool.
await Task.Run(() => DoProcessing(progress));
textBox1.Text = "Done!";
}
public void DoProcessing(IProgress<int> progress)
{
for (int i = 0; i != 100; ++i)
{
Thread.Sleep(100); // CPU-bound work
if (progress != null)
progress.Report(i);
}
}
Now, a little bit of self-promotion :) I created an IEnumerable<T> extension to run a parallel for with event callbacks that can directly modify the UI. You can have a look at it here:
https://github.com/jotaelesalinas/csharp-forallp
Hope it helps!

Categories