Calling asynchronous function synchronously is more responsive - winforms

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

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.

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

Running long tasks without freezing the UI

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.

Is there "LoadComplete" such an event in Windows Forms?

I want my windows form to be loaded first, render its children and all. After that load heavy data in it. This is why I am looking for any event which I could use just after form loading is complete.
Any thoughts on this?
I have never found a better solution than Activated; although that is raised every time the form receives focus - so you need to filter out all the times after the first:
bool _firstActivation = true;
void Form1_Activated(object sender, EventArgs e)
{
if (_firstActivation)
{
_firstActivation = false;
OnFirstActivation();
}
}
private void OnFirstActivation()
{
}
Perhaps you're looking for the Form.Shown event. If you're doing a lot of intensive work though, perhaps you should be using a background thread anyway to avoid locking up the UI.
Like MikeP said you want to handle the Form.Shown event just once. So just attach to the even and detach once done.
private void frmMain_Load(object sender, System.EventArgs e)
{
// Do stuff in form load.
Shown += FirstShown;
}
private void FirstShown(object sender, EventArgs eventArgs)
{
Refresh();
// Do something here
// Detach from this event.
Shown -= FirstShown;
}
I do that in a way that I fire a timer with duration of 1, and kill it in the event, and with that method, I know that message loop will be empty and form initialization will be complete when my event comes.
Event is set up from Form_OnLoad() method.

Can I use Monitor.Enter/Exit (c# lock) with WPF without fear of reentrancy bugs?

If I use Monitor.Enter/Exit (through the c# lock syntax) in a WPF application, can the dispatcher cause re-entrance?
In the sample below, presuming OnTextChanged is called when the text in a textbox changes, could the call to _worker.RunWorkerAsync() be called incorrectly?
public class SomeClass
{
private object _locker = new object();
private bool _running = false;
private BackgroundWorker _worker;
public void SomeClass()
{
// initialize worker...
}
void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
lock (_locker)
_running = false;
}
void _worker_DoWork(object sender, DoWorkEventArgs e)
{
// ... do something time consuming ...
}
private void OnTextChanged()
{
lock(_locker)
{
if (!_running)
{
_worker.RunWorkerAsync();
_running = true;
}
}
}
}
I believe it's possible, but I've not been able to reproduce this. Does WPF somehow prevent the dispatcher from invoking waiting tasks when waiting on monitor?
Not sure what you fear. Both OnTextChanged and RunWorkerCompleted run on the UI thread. It won't be re-entrant, you don't need the lock either. Either method can only start running when the UI thread is idle, pumping the message loop.
While not directly related to your question, you could run into register caching issues if you don't mark _running as volatile.
Actually this isn't strictly true, as you are not using a double-checked lock. I've left the information related to volatile there anyway, for your reference.

Resources