Parallel Tasks In WPF - c#

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.

Related

WPF Task Manager: Handling Refreshing CPU Load Value?

I am currently somewhat new to c#/wpf (and coding in general). I decided to start another project, being a custom made "task manager" of sorts.
(While I use binding, this is NOT a MVVM project, so all answers welcome)
If you have ever opened task manager, you know that one of the main helpful tools it provides is a updating view of CPU/RAM/Whatever usage. Telling the user what percent of the resource they are using.
My problem is not getting the CPU percentage. I am unsure on how to refresh the text property for CPU load in the UI efficiently.
My first thought was that I should create a Background worker (which is probably correct) to separate the thread loads. However, I can't seem to wrap my mind on the solution to implement the background workers in a useful way.
The code is currently set up in this fashion:
When page is loaded, public BgWrk creates a new instance of it self.
Adds task to be called when ran.
BgWrk is ran.
New instance of method to be called is made.
Dispatcher is invoked on main thread to update UI.
Invoke consists of setting public string PerCpu (bound in other class, using INotifyPropertyChanged & all) on the return value of "grabber"'s CpuPerUsed.
BgWrk disposed.
Program loops (this is most likely the problem).
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
BgWrk = new BackgroundWorker();
BgWrk.DoWork += new DoWorkEventHandler(BackgroundWorker1_DoWork);
BgWrk.RunWorkerAsync();
}
private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
CpuInfoGrabber grabber = new CpuInfoGrabber();
Application.Current.Dispatcher.Invoke(new Action (() => Bnd.PerCpu = grabber.CpuPerUsed()));
BgWrk.Dispose();
}
}
Again the code works, but it is WAY to slow due to the load of retrieving all of that data. Any suggestions on how to make this work well are appreciated!
Thanks
Instead of looping you could use a timer to periodically poll for the CPU usage.
class Test
{
private System.Timers.Timer _timer;
public Test( )
{
_timer = new System.Timers.Timer
{
// Interval set to 1 millisecond.
Interval = 1,
AutoReset = true,
};
_timer.Elapsed += _timer_Elapsed;
_timer.Enabled = true;
_timer.Start( );
}
private void _timer_Elapsed( object sender, System.Timers.ElapsedEventArgs e )
{
// This handler is not executed on the gui thread so
// you'll have to marshal the call to the gui thread
// and then update your property.
var grabber = new CpuInfoGrabber();
var data = grabber.CpuPerUsed();
Application.Current.Dispatcher.Invoke( ( ) => Bnd.PerCpu = data );
}
}
I'd use Task.Run instead of a BackgroundWorker in your case:
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
//Keep it running for 5 minutes
CancellationTokenSource cts = new CancellationTokenSource(new TimeSpan(hours: 0, minutes: 5, seconds: 0));
//Keep it running until user closes the app
//CancellationTokenSource cts = new CancellationTokenSource();
//Go to a different thread
Task.Run(() =>
{
//Some dummy variable
long millisecondsSlept = 0;
//Make sure cancellation not requested
while (!cts.Token.IsCancellationRequested)
{
//Some heavy operation here
Thread.Sleep(500);
millisecondsSlept += 500;
//Update UI with the results of the heavy operation
Application.Current.Dispatcher.Invoke(() => txtCpu.Text = millisecondsSlept.ToString());
}
}, cts.Token);
}

Using a BlockingCollection to queue Tasks

I am trying to create a way to queue up Tasks to run, so I have tried to implement it using a BlockingCollection. The problem I find is whenever I try to add the Task, the Task executes. Sample code as below:
private void button1_Click(object sender, EventArgs e)
{
textBox2.Clear();
for (int i = 0; i < 10; i++)
_processCollection.Add(BigTask(i));
}
static BlockingCollection<Task> _processCollection = new BlockingCollection<Task>();
Thread ConsumerThread = new Thread(LaunchConsumer);
private static async void LaunchConsumer()
{
while (true)
{
var processTask = _processCollection.Take();
await Task.Run(() => processTask);
}
}
async Task BigTask(int i)
{
await Task.Delay(5000);
textBox2.AppendText($"Text{i}\n");
}
What seems to happen in debug is all the tasks seem to run as they are added into the blocking collection. I tried switching the blocking collection to use Action, but that just leads to nothing happening. As below (only changes shown):
private void button1_Click(object sender, EventArgs e)
{
textBox2.Clear();
for (int i = 0; i < 10; i++)
{
int iC = i;
_processCollection.Add(async () => await BigTask(iC));
}
}
static BlockingCollection<Action> _processCollection = new BlockingCollection<Action>();
Thread ConsumerThread = new Thread(LaunchConsumer);
private static async void LaunchConsumer()
{
while (true)
{
var processTask = _processCollection.Take();
await Task.Run(processTask);
}
}
I feel like I have made some small error somewhere, because it feels like this should work. I have tried to find someone doing something similar but have had no luck, which makes me think maybe my concept is flawed so feel free to suggest an alternative.
_processCollection.Add(BigTask(i)); doesn't work because this calls BigTask(i) immediately, and when that is called, the work starts.
You were on the right track by wrapping this in a separate BigTask launcher, but by using Action, you don't provide your LaunchConsumer with any means to track the progress. await Task.Run(processTask) will continue pretty much immediately with the next task. You need to use Func<Task> to avoid that.
The reason you don't see any results is likely unrelated. Now that you manage to launch the task from your newly created thread, the call to textBox2.AppendText is no longer done from the UI thread. That's not supported. Only the UI thread can access UI objects. You can use textBox2.Invoke to pass an action back to the UI thread, and that action can then call AppendText.
Tested working code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ConsumerThread.Start();
}
private void button1_Click(object sender, EventArgs e)
{
textBox2.Clear();
foreach (var i in Enumerable.Range(0, 10))
_processCollection.Add(() => BigTask(i));
}
static BlockingCollection<Func<Task>> _processCollection = new BlockingCollection<Func<Task>>();
Thread ConsumerThread = new Thread(LaunchConsumer);
private static async void LaunchConsumer()
{
while (true)
{
var processTask = _processCollection.Take();
await Task.Run(processTask);
}
}
async Task BigTask(int i)
{
await Task.Delay(5000);
textBox2.Invoke(new Action(() => textBox2.AppendText($"Text{i}\n")));
}
}
That said, BlockingCollection is not really the best collection type to use here. It dedicates one thread to pretty much nothing but waiting. Also, Task.Run when you're already in a background thread can admittedly sometimes be useful, but doesn't add anything here. What to do instead depends on your needs. Whether all tasks are known beforehand makes a difference. Whether you may want multiple consumers makes a difference. Other things I haven't thought of may also make a difference.

Dispatcher.BeginInvoke() not running asynchronously

Here's a simplified version of what I want to do:
onClick a button, a aNewMethod() would run asynchronously to keep UI responsive. That's it!
I've read some answers and here's what i could come up with :
private async void button_Click(object sender, RoutedEventArgs e)
{
Task task = Task.Run(() => aNewMethod());
await task;
}
private void aNewMethod()
{
if (progress.Value == 0)
{
//Heavy work
for (int i = 1; i < 1000000000; i++) { }
progress.Value = 100;
}
}
As you may have thought, this throws a System.InvalidOperationException at if(progress.Value== 0) saying :
The calling thread cannot access this object because a different
thread owns it.
after some Googling, I've read that I need a Dispatcher.BeginInvoke() method to update/use UI controls, so I did this :
private void aNewMethod()
{
Dispatcher.BeginInvoke((Action)delegate {
if (progress.Value == 0)
{
//Heavy work
for (int i = 1; i < 1000000000; i++) { }
progress.Value = 100;
}
});
}
This solved the System.InvalidOperationException but it's not running asynchronously as the UI still freezes at for loop
So the question is : How to run the aNewMethod(); asynchronously and still update and interact with UI controls ?
The Dispatcher runs in the UI thread. It handles your UI, and executes actions you pass to it via BeginInvoke etc.
But it can only handle one thing at a time; when it's busy handling your action, it won't update the UI in the meantime, so the UI freezes in the meantime.
If you want to keep your UI responsive, you'd need to run the heavy load functions in a seperate, non-UI thread. Within those functions running on another thread, you can call the dispatcher whenever you need access to the UI - ideally, only very briefly for the purpose of updating UI elements.
So in other words, you'd want to be running your sleep function in a seperate thread, and then just make a call to the Dispatcher from your own thread when you need to set the progress value. Something like
private void button_Click(object sender, RoutedEventArgs e)
{
Task task = Task.Run(() => aNewMethod()); // will call aNewMethod on the thread pool
}
private void aNewMethod()
{
double progressValue = 0;
Dispatcher.Invoke(() => progressValue = progress.Value);
if (progressValue == 0)
{
Thread.Sleep(3000); // still executes on the threadpool (see above), so not blocking UI
Dispatcher.Invoke(() => progress.Value = 100 ); // call the dispatcher to access UI
}
}
With you current implementation, there is no need to use Thread.Start, as in that method you are just sleeping it for some time and accessing UI thread objects there which is not allowed
.In your scenario a better way of doing is that you should not use Thread.Sleep instead of that you should be doing it with Task.Delay like:
private async void button_Click(object sender, RoutedEventArgs e)
{
if (progress.Value == 0)
{
await Task.Delay(3000);
progress.Value = 100;
}
}
Now you don't need Dispatcher.Invoke, and credit goes to async and await keywords as statements after await will be executing in the same calling synchronization context from where we did async call which is UI thread in this case.
onClick a button, a aNewMethod() would run asynchronously to keep UI responsive.
Actually, it's running synchronously on a background thread. Task.Run is perfectly appropriate for this.
after some Googling, I've read that I need a Dispatcher.BeginInvoke() method to update/use UI controls
Unfortunately, this is one area where Google will certainly mislead you. Dispatcher is not the best solution here.
Instead, you should use IProgress<T>/Progress<T>, as such:
private async void button_Click(object sender, RoutedEventArgs e)
{
var progress = new Progress<int>(value => { this.progress.Value = value; });
await Task.Run(() => aNewMethod(progress));
}
private void aNewMethod(IProgress<int> progress)
{
//Heavy work
for (int i = 1; i < 1000000000; i++) { }
progress.Report(100);
}

Why does Thread.Sleep() freeze the Form?

I try to experiment with Thread.Sleep(). I created basic Windows Forms application with one button.
private void button1_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(DoStuff);
thread1.Start();
for (int i = 0; i < 100000; i++)
{
Thread.Sleep(500);
button1.Text +=".";
}
}
public void DoStuff()
{
//DoStuff
}
When I click my button the DoStuff method works fine, but the GUI freezes and nothing happens. Can someone explain me why?
Thread.Sleep just sleeps the current thread (i.e. stops it from doing anything, such as redrawing, processing clicks etc), which in your case is the UI thread. If you put the Sleep in DoStuff you wouldn't experience the block as you'd be on a separate thread although you wouldn't be able to update button1. Depending on the version of .NET you're using consider using the Task Parallel Library, something like this:
private TaskScheduler _uiScheduler;
public Form1()
{
InitializeComponent();
_uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
private void button1_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(DoStuff);
thread1.Start();
// Create a task on a new thread.
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 100000; i++)
{
Thread.Sleep(500);
// Create a new task on the UI thread to update the button
Task.Factory.StartNew(() =>
{ button1.Text += "."; }, CancellationToken.None, TaskCreationOptions.None, _uiScheduler);
}
});
}
To keep the UI active, you need for the main UI thread to service its message pump. It can only do that when it is not handling UI events. In your case the function
private void button1_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(DoStuff);
thread1.Start();
for (int i = 0; i < 100000; i++)
{
Thread.Sleep(500);
button1.Text +=".";
}
}
does not return for around 100000*500 milliseconds. While this event handler is executing, the UI thread is busy. It is executing this event handler. As such it is not able to service the message pump. Hence your application's UI freezes.
For that you better use a Timer but if you want your current code to work you need to add Application.DoEvents(); after you update the button.Label += "."
If you're new to multithreading, I strongly encourage you to look at the Task Parallel Library (TPL). It simplifies threading, and gives you tools to help guarantee callback (continuation) threads occur on the UI thread.
The TPL is in the System.Threading.Tasks namespace.
Update: just seen your comment about .Net v2. TPL was introduced in .NET v3.5 or possibly as late as v4.
EDIT: After programming for a few more years, I now know how terrible of a practice this is. DO NOT DO ANYTHING I SUGGESTED BELOW. It's all crap. A more proper solution would be doing all of your intensive methods async all together. Regardless, don't do what I mention below.
All The methods above do work however, I do recommend just using an async void.
Sleep() just pauses the current thread for int amount of milliseconds, and if your whole program runs off of 1 thread, it'll pause the whole program. Don't quote me on this, I do believe that async creates a new thread specifically for that function.
Below I've included a better sleep function.
To call the function asleep(milliseconds),
replace "milliseconds" with how many milliseconds you wish to sleep for.
Function Code:
public async void asleep(int time){
await Task.Delay(time)
}
Re-arrange code as following
private void button1_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(DoStuff);
thread1.Start();
}
public void DoStuff()
{
for (int i = 0; i < 100000; i++)
{
Thread.Sleep(500);
//Invoke goes here
}
}
Now you run your WORK in a separate thread and release your UI thread for usual work (Drawing related or other work)
NOTE - Now you will need Invoke methods to change Button text , else you will get warning for "Cross-thread operation not valid"
More on Invokes - How to update the GUI from another thread in C#?

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