Running long tasks without freezing the UI - wpf

I am trying to perform an action in the background, without freezing the UI.
Of course, I could use BackgroundWorker for this.
However, I'd like to do it with the Task API only.
I tried:
async void OnTestLoaded(object sender, RoutedEventArgs e)
{
await LongOperation();
}
// It freezes the UI
and
async void OnTestLoaded(object sender, RoutedEventArgs e)
{
var task = Task.Run(()=> LongOperation());
task.Wait();
}
// It freezes the UI
So should I go back to BackgroundWorker? Or is there a solution using Tasks only?

You were pretty close.
async void OnTestLoaded(object sender, RoutedEventArgs e)
{
await Task.Run(() => LongOperation());
}
async does not execute a method on a thread pool thread.
Task.Run executes an operation on a thread pool thread and returns a Task representing that operation.
If you use Task.Wait in an async method, you're doing it wrong. You should await tasks in async methods, never block on them.

Related

WPF: What is the difference between an Action and an Action that executes the Action?

Since Pipe_Disconnected is called by another thread, MainWindow.Close must be called by Dispatcher.
What is the difference between the codes below?
I am using .Net7.
Work
private void Pipe_Disconnected(object sender, object e)
{
Application.Current.Dispatcher.InvokeAsync(() => Application.Current.MainWindow.Close());
}
Not Work
private void Pipe_Disconnected(object sender, object e)
{
Application.Current.Dispatcher.InvokeAsync(Application.Current.MainWindow.Close);
}
Not Work
private void Pipe_Disconnected(object sender, object e)
{
var temp = new Action(Application.Current.MainWindow.Close);
Application.Current.Dispatcher.InvokeAsync(temp);
}
The practical difference when Application.MainWindow is being accessed.
In the first example, the property is accessed in dispatcher thread.
In the second and third it is accessed from another thread.
If you examine stacktrace of the exception, you won't see the Close method, because it is never called. The Exception is thrown when accessing MainWindows instance. The Dispatcher. InvokeAsync won't even get a chance so be called.

Calling asynchronous function synchronously is more responsive

I have an asynchronous method like this:
private async Task TaskAsync()
{
await Task.Run(() => Task.Delay(2000));
}
I then call it in a button-click event, which I've declared like this:
private async void button1_Click(object sender, EventArgs e)
{
await TaskAsync();
MessageBox.Show("Afterwards.");
}
Now, when I click on the button, TaskAsync() is literally awaited on, and the message box isn't shown until TaskAsync() has finished executing. However, when I remove the await command when calling TaskAsync() in the click event, then execution immediately jumps to the message box.
Am I doing something wrong here? Is this normal behavior of async...await?
My project is a .NET Core 5 C# winform project.
There are two problems here. First, the TaskAsync method is using Task.Run to run another async method. That just wastes a thread. It should be just :
private async Task TaskAsync()
{
await Task.Delay(2000);
}
if not
private Task TaskAsync()=>Task.Delay(2000);
Second, if a Task isn't awaited execution will proceed immediately. That's the whole point of using await - awaiting an already executing asynchronous task to complete without blocking the calling thread.
The original code is equivalent to :
private async void button1_Click(object sender, EventArgs e)
{
await Task.Delay(2000);
MessageBox.Show("Afterwards.");
}
Without await, the task returned by Task.Delay() will be ignored and the message box will be displayed immediately.
If one wanted to start a long operation, eg reading a big file, and display a message at the same time, the task can be stored in a field and awaited after the dialog box is closed:
private async void button1_Click(object sender, EventArgs e)
{
var task=File.ReadAllTextAsync(...);
MessageBox.Show("Reading a file");
var text=await task;
MessageBox.Show("Afterwards.");
}
or
private async void button1_Click(object sender, EventArgs e)
{
var task=Task.Run(()=>SomeBackgroundProcessing(someArgs));
MessageBox.Show("Processing");
var text=await task;
MessageBox.Show("Afterwards.");
}

Add items to listbox By thread in WPF using C#

I am using the code below, however it is causing my application to hang and I am unsure as to why. Would anyone be able to help me out here?
void put_items() {
listb.Dispatcher.BeginInvoke(new Action(() =>
{
for (int i = 0; i < 9000000; i++)
{
listb.Items.Add(i.ToString());
}
}));
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Thread mythread = new Thread(put_items);
mythread.Start();
}
If you want to update any UI controls this has to be done in the UI thread. By using the Dispatcher you force your application to execute the code within BeginInvoke-block to be executed on the ui thread.
Depending on how time consuming the work for one item in the for loop is, you could process a bunch of items (say 10 or 100) and then update the ui by using the dispatcher. Notice that each call of Dispatcher.BeginInvoke needs some time (maybe 500 ms).
Another way would be using an event aggregator see here. Then your class containing the button click method would register to the event aggregator and in the thread you would just need the instance of the aggregator and call ea.Publish(new YourCustomEvent(yourItemToUpdateUI)).
This approach is really nice if your application is going to be complex.
thank you but I think that code not work in netframwork 3.5 I think that I resolve By this code
public void put_items()
{
for (int i = 0; i < 999999999; i++)
{
this.Dispatcher.Invoke(new Action (() =>
{
listb.Items.Add(i.ToString());
}));
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
new Thread(put_items).Start();
}

How to hide a window using another thread?

I've create a WPF application with NotifyIcon, and I use this.Hide() to make it minimized, but now I hope it could be minimized once it had been executed, so I invoke this.Hide() in the method MainWindow_Loaded, but once I start the app, the content of the window turn to be all dark.
Then I try to invoke this.Hide() in another thread, and this is what my code looks like...
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
...
Thread thread = new Thread(DoWork);
thread.Start();
Console.WriteLine("thread start");
while (!thread.IsAlive) ;
Thread.Sleep(500);
thread.Join();
Console.WriteLine("thread has terminated.");
...
}
public void DoWork()
{
this.Hide();
}
then I encounter the problem, when DoWork() is invoked, it showed Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on. What should I do to avoid this? Thanks!
You need to use Dispatcher object.
Dispatcher.BeginInvoke((Action) (() =>
{
// your code
}));

Wait until a background worker is completed without locking out the main thread

I'm trying to have a background worker running with a spinner running in the main thread and I would like to wait until the background worker is finished. Currently I can have one or the other. I have tried using a Auto Reset Event but that locks out the main thread therefore not displaying the spinner. Is there something similar to Auto Reset Event that does not lock out the main UI.
Here is some of my code
BackgroundWorker _bWorker = new BackgroundWorker();
_bWorker.DoWork += _bWorker_DoWork ;
_bWorker.RunWorkerCompleted += _bWorker_RunWorkerCompleted;
AutoResetEvent are = new AutoResetEvent(false);
_bWorker.RunWorkerAsync();
// Wait here until the background worker is finished
are.WaitOne();
...
private void _bWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Show spinner
WaitSpinnerAdornerText = "Working";
IsWaitSpinnerVisible = true;
...
are.Set();
}
private void _bWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
IsWaitSpinnerVisible = false;
...
}
Any ideas? Any help is welcome! Thanks in advance.
The whole point of using a BackgroundWorker is that you don't 'wait' for it to complete. Just remove the AutoResetEvent and trigger any logic that currently resides after the RunWorkerAsync in the _RunWorkerCompleted.
Alternatively you could look into using the Task Parallel Library and have that code run as a continuation.
Lastly you could look into using the Async CTP for .net 4.0 in which the async/await features behave nearly identically to how you want to write the code.

Resources