I have a problem in a Windows Forms application where I use PerformClick to call an async event handler. It seems that the event handler doesn't await but just returns immediately. I have created this simple application to show the problem (it's just a form with 3 buttons, easy to test yourself):
string message = "Not Started";
private async void button1_Click(object sender, EventArgs e)
{
await MyMethodAsync();
}
private void button2_Click(object sender, EventArgs e)
{
button1.PerformClick();
MessageBox.Show(message);
}
private void button3_Click(object sender, EventArgs e)
{
MessageBox.Show(message);
}
private async Task MyMethodAsync()
{
message = "Started";
await Task.Delay(2000);
message = "Finished";
}
The interesting problem here is, what do you think message shows, when I click Button2?
Surprisingly, it shows "Started", not "Finished", as you would expect. In other Words, it doesn't await MyMethod(), it just starts the Task, then continues.
Edit:
In this simple code I can make it Work by calling await Method() directly from Button2 event handler, like this:
private async void button2_Click(object sender, EventArgs e)
{
await MyMethodAsync();
MessageBox.Show(message);
}
Now, it waits 2 seconds and displays 'Finished'.
What is going on here? Why doesn't it work when using PerformClick?
Conclusion:
Ok, now I get it, the conclusion is:
Never call PerformClick if the eventhandler is async. It will not await!
Never call an async eventhandler directly. It will not await!
What's left is the lack of documentation on this:
Button.PerformClick should have a Warning on the doc page:
Button.PerformClick
"Calling PerformClick will not await async eventhandlers."
Calling an async void method (or eventhandler) should give a compiler Warning: "You're calling an async void method, it will not be awaited!"
You seem to have some misconceptions about how async/await and/or PerformClick() work. To illustrate the problem, consider the following code:
Note: the compiler will give us a warning but let's ignore that for the sake of testing.
private async Task MyMethodAsync()
{
await Task.Delay(2000);
message = "Finished"; // The execution of this line will be delayed by 2 seconds.
}
private void button2_Click(object sender, EventArgs e)
{
message = "Started";
MyMethodAsync(); // Since this method is not awaited,
MessageBox.Show(message); // the execution of this line will NOT be delayed.
}
Now, what do you expect the MessageBox to show? You'd probably say "started".1 Why? Because we didn't await the MyMethodAsync() method; the code inside that method runs asynchronously but we didn't wait for it to complete, we just continued to the next line where the value of message isn't yet changed.
If you understand that behavior so far, the rest should be easy. So, let's change the above code a little bit:
private async void button1_Click(object sender, EventArgs e)
{
await Task.Delay(2000);
message = "Finished"; // The execution of this line will be delayed by 2 seconds.
}
private void button2_Click(object sender, EventArgs e)
{
message = "Started";
button1_Click(null, null); // Since this "method" is not awaited,
MessageBox.Show(message); // the execution of this line will NOT be delayed.
}
Now, all I did was that I moved the code that was inside the async method MyMethodAsync() into the async event handler button1_Click, and then I called that event handler using button1_Click(null, null). Is there a difference between this and the first code? No, it's essentially the same thing; in both cases, I called an async method without awaiting it.2
If you agree with me so far, you probably already understand why your code doesn't work as expected. The code above (in the second case) is nearly identical to yours. The difference is that I used button1_Click(null, null) instead of button1.PerfomClick(), which essentially does the same thing.3
The solution:
If you want to wait for the code in button1_Click to be finished, you need to move everything inside button1_Click (as long as it's asynchronous code) into an async method and then await it in both button1_Click and button2_Click. This is exactly what you did in your "Edit" section but be aware that button2_Click will need to have an async signature as well.
1 If you thought the answer was something else, then you might want to check this article which explains the warning.
2 The only difference is that in the first case, we could solve the problem by awaiting the method, however, in the second case, we can't do that because the "method" is not awaitable because the return type is void even though it has an async signature.
3Actually, there are some differences between the two (e.g., the validation logic in PerformClick()), but those differences don't affect the end result in our current situation.
Related
I've been trying for hours to wait for an async method to complete before continue with the application. If I debug everything works great because I'm actually waiting for the async method to complete but without debugging the code executes while calling the method and nothing works.
Here is my code:
private async void MenuPage_Loaded(object sender, RoutedEventArgs e)
{
MenuItems = await SetItemSource();
ItemSource = new ObservableCollection<AlphaKeyGroup<Menu>>((AlphaKeyGroup<Menu>.CreateGroups(MenuItems, CultureInfo.CurrentUICulture, s => s.MenuName, true)));
((CollectionViewSource)Resources["MenuGroups"]).Source = ItemSource;
}
private async Task<ObservableCollection<Menu>> SetItemSource()
{
return await MyWinService.GetMenuEntriesAsync();
}
Remove the async/await in the SetItemSource method. You are returning an awaitable task that is telling MenuPage_Loaded to continue at that point.
Async is not required in the second method as there is no continuation.
Okay, I did this:
private async void MenuPage_Loaded(object sender, RoutedEventArgs e)
{
var MenuItemsTask = MyWinService.GetMenuEntriesAsync();
MenuItems = await MenuItemsTask;
ItemSource = new ObservableCollection<AlphaKeyGroup<Menu>>((AlphaKeyGroup<Menu>.CreateGroups(MenuItems, CultureInfo.CurrentUICulture, s => s.MenuName, true)));
((CollectionViewSource)Resources["MenuGroups"]).Source = ItemSource;
}
I swear it was not working but I republished my ServiceReference (without changing anything) and suddenly it works, I mean, I don't know what is happening, I hate to not know what happened haha.
Thanks for the help.
I have a task that runs in the form_load event of a usercontrol in winforms:
private void ucDeviceInsert_Load(object sender, EventArgs e)
{
System.Threading.Tasks.Task getTBox = System.Threading.Tasks.Task.Run(async () =>
{
await AVeryLongRunningProccess();
});
pbImage.Image = Properties.Resources.Remove;
getTBox.Wait();
pbImage.Image = Properties.Resources.Insert;
btnNext.Visible = true;
tmrDeviceInsert.Enabled = true;
tmrDeviceInsert.Start();
}
private void tmrDeviceInsert_Tick(object sender, EventArgs e)
{
Next();
}
I change the image of the picture box to inform the user the progress of the long running process. That part works fine, however the button doesn't show, and the timer never starts. I've stepped through the code, and I can confirm that it is running without any problems, which makes this even more baffling. Any ideas what would be causing this issue?
Task.Run is for pushing CPU-intensive work off the UI thread. Since you're calling an asynchronous method, I suspect it's not CPU-intensive.
So, you can just use async and await:
private async void ucDeviceInsert_Load(object sender, EventArgs e)
{
pbImage.Image = Properties.Resources.Remove;
await AVeryLongRunningProccess();
pbImage.Image = Properties.Resources.Insert;
btnNext.Visible = true;
tmrDeviceInsert.Enabled = true;
tmrDeviceInsert.Start();
}
Note that at the await, the UI is shown and the user can interact with it (that's the point).
getTBox.Wait() is going to try to complete that task synchronously. Therefore, the rest of the code after it won't happen until after the task completes.
I'd think you don't want your task to run synchronously at all, but rather handle its completion asynchronously, something like this:
getTBox.ContinueWith(() => updateStatusInUI());
I am learning to implement async/await on one of my task which takes a long time to complete. Once the task is completed, it should update the DGV.
private async void frmA_DragDrop(object sender, DragEventArgs e)
{
// Some codes to accept dropped files
// Run task asynchronously
Task<DataTable> task = Task.Run(() => PDF.CheckPDFs(Files, tblPDFs));
dgvPDF.DataSource = await task;
}
I found that after the task is completed, it is not updating the DGV. So, I added the InvokeRequired bit to manually update the table and also to prevent cross-thread exception.
// Update UI
if (dgvPDF.InvokeRequired)
{
dgvPDF.Invoke((MethodInvoker)(() => { dgvPDF.Refresh(); }));
}
else
{
dgvPDF.Refresh();
}
I also have a separate event handler which highlights the row red is a file is invalid:
private void dgvPDF_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
{
if (dgvPDF["Pages",e.RowIndex].Value.ToString() == "0")
dgvPDF.Rows[e.RowIndex].DefaultCellStyle.BackColor = System.Drawing.Color.MistyRose;
}
What happened is that the RowAdded event handler is not triggered at all. If I made the code synchronous i.e removing await/async and invoke, everything works fine except with the UI freezing
My question is using InvokedRequired the correct way to update the UI in async await? What can I do to fix this issue?
no, you don't have to "invoke", the context will switch back after async method call complete, i.e., the statements after await will get executed in UI thread.
I get data from database on a click.
I have an event handler which when triggered should show "data retrieving..." in status bar and should change to "Ready" again just before the event handler ends.
But the text updates only once, the second Ready one. How is it generally done?
private void Next_Click(object sender, RoutedEventArgs e){
this.footerText = "Waiting for dataRetreival";
someRandomTimeTakingMethod(); //Gets Data from DB.
this.footerText = "Ready";
}
Even though code executes line 2, the view updates only when the function is over, ie only the second one actually works.
You should put your data-intensive work on a background thread so the UI can update properly. This provides the best user experience.
To elaborate on FZysset's answer with some code...
private async void Next_Click(object sender, RoutedEventArgs e)
{
footerText.Text = "Waiting for dataRetreival";
IsEnabled = false;
await SomeRandomTimeTakingMethodAsync();
IsEnabled = true;
footerText.Text = "Ready";
}
private async Task SomeRandomTimeTakingMethodAsync()
{
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(2, 5)));
// or await Task.Run(() => { ... });
}
The above example allows you to leverage await/async that was introduced in .NET 4.5. Notice how nicely it flows? No nonsense!
We're putting stuff onto the background thread so the UI can remain unblocked (thus it will show your updates to your status bar and allow user interaction.) Of course, you have to be careful not to update anything on the UI from your background thread.
If you are using an older version of .NET, you can just use TPL without async/await:
private void Next_Click(object sender, RoutedEventArgs e)
{
footerText.Text = "Waiting for dataRetreival";
IsEnabled = false;
Task.Factory.StartNew(() =>
{
SomeRandomTimeTakingMethod();
}).ContinueWith(t =>
{
IsEnabled = true;
footerText.Text = "Ready";
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private void SomeRandomTimeTakingMethod()
{
Thread.Sleep(TimeSpan.FromSeconds(new Random().Next(2, 5)));
}
Two important things to note about the latter example:
You must provide TaskScheduler.FromCurrentSynchronizationContext() to the ContinueWith call, or you will encounter exceptions because the continuation is not on the UI thread. You must get the context in a method that isn't running on a background thread.
You will want to check for exceptions on the Task object in your ContinueWith.
This example is very rudimentary though. If you were to have a bunch of background operations kicked off with click handlers, you'd want to give yourself some helper classes/services to make life easier. (And investigate MVVM, which I cannot tell if you are using.)
A colleague of mine gave a presentation on using various asynchronous patterns in C# and .NET. You can check it out here: https://github.com/mtusk/TplLunchAndLearn
That's because you're "someRandomTimeTakingMethod" is launched on the UI Thread. Therefore it will not update the view until it is finished.
To go around this you have the following possibilities :
Make your method "someRandom..." asynchronous with a task, and use the await operator : http://msdn.microsoft.com/en-us/library/hh191443.aspx
Launch your randomTimeTaking method into a thread, and launch an event when your execution is finished, to update the footer text
I strongly recommend you the first option, for some sample : http://msdn.microsoft.com/en-us/library/hh873191.aspx
You need to run those lines asynchronously. You can do that using the Task class:
private void Next_Click(object sender, RoutedEventArgs e){
Task.Factory.StartNew(() => footerText = "Waiting for dataRetreival");
someRandomTimeTakingMethod(); //Gets Data from DB.
Task.Factory.StartNew(() => footerText = "Ready");
}
There is one way to do it using Dispatcher. The original post is here.
The code is:-
private void Next_Click(object sender, RoutedEventArgs e){
UpdateUI("Please wait for data retrieval", delegate() { someRandomTimeTakingMethod(); });
this.footerText = "Ready";
}
public delegate void NoArgsDelegate();
public void UpdateUI(string description, NoArgsDelegate operation)
{
this.FooterText= description;
DispatcherFrame frame = new DispatcherFrame();
DispatcherOperation dispatcherOperation = Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ContextIdle, operation);
dispatcherOperation.Completed += delegate(object sender, EventArgs e)
{
frame.Continue = false;
};
Dispatcher.PushFrame(frame);
}
If my understanding is right, this uses Asynchronous programming, not different thread. The thread will update UI first and then call the someRandomTimeTakingMethod().
I am playing with async await feature of C#. Things work as expected when I use it with UI thread. But when I use it in a non-UI thread it doesn't work as expected. Consider the code below
private void Click_Button(object sender, RoutedEventArgs e)
{
var bg = new BackgroundWorker();
bg.DoWork += BgDoWork;
bg.RunWorkerCompleted += BgOnRunWorkerCompleted;
bg.RunWorkerAsync();
}
private void BgOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
}
private async void BgDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
await Method();
}
private static async Task Method()
{
for (int i = int.MinValue; i < int.MaxValue; i++)
{
var http = new HttpClient();
var tsk = await http.GetAsync("http://www.ebay.com");
}
}
When I execute this code, background thread don't wait for long running task in Method to complete. Instead it instantly executes the BgOnRunWorkerCompleted after calling Method. Why is that so? What am I missing here?
P.S: I am not interested in alternate ways or correct ways of doing this. I want to know what is actually happening behind the scene in this case? Why is it not waiting?
So, BgDoWork is called on a background thread by the BackgroundWorker
It calls Method, which starts the loop and calls http.GetAsync
GetAsync returns a Task and continues it's work on another thread.
You await the Task which, because the Task has not completed, returns from Method
Similarly, the await in BgDoWork returns another Task
So, the BackgroundWorker sees that BgDoWork has returned and assumes it has completed.
It then raises RunWorkerCompleted
Basically, don't mix BackgroundWorker with async / await!
Basically, there are two problems with your code:
BackgroundWorker wasn't updated to work with async. And the whole point of async methods is that they actually return the first time they await something that's not finished yet, instead of blocking. So, when your method returns (after an await), BackgroundWorker thinks it's completed and raises RunWorkerCompleted.
BgDoWork() is an async void method. Such methods are “fire and forget”, you can't wait for them to complete. So, if you run your method with something that understands async, you would also need to change it to async Task method.
You said you aren't looking for alternatives, but I think it might help you understand the problem if I provided one. Assuming that BgDoWork() should run on a background thread and BgOnRunWorkerCompleted() should run back on the UI thread, you can use code like this:
private async void Click_Button(object sender, RoutedEventArgs e)
{
await Task.Run((Func<Task>)BgDoWork);
BgOnRunWorkerCompleted();
}
private void BgOnRunWorkerCompleted()
{
}
private async Task BgDoWork()
{
await Method();
}
Here, Task.Run() works as an async-aware alternative to BackgroundWorker (it runs the method on a background thread and returns a Task that can be used to wait until it actually completes). After await in Click_Button(), you're back on the UI thread, so that's where BgOnRunWorkerCompleted() will run. Click_Button() is an async void method and this is pretty much the only situation where you would want to use one: in an event handler method, that you don't need to wait on.
I think you need some reason for the background thread to stay alive while it's waiting for Method() to complete. Having an outstanding continuation is not enough to keep a thread alive, so your background worker terminates before Method() completes.
You can prove this to yourself by changing your code so that the background thread does a Thread.Sleep after the await Method(). That's almost certainly not the real behaviour you want, but if the thread sleeps for long enough you'll see Method() complete.
Following is how DoWork is raised and handled. (code retrieved using Reflector tool).
private void WorkerThreadStart(object argument)
{
object result = null;
Exception error = null;
bool cancelled = false;
try
{
DoWorkEventArgs e = new DoWorkEventArgs(argument);
this.OnDoWork(e);
if (e.Cancel)
{
cancelled = true;
}
else
{
result = e.Result;
}
}
catch (Exception exception2)
{
error = exception2;
}
RunWorkerCompletedEventArgs arg = new RunWorkerCompletedEventArgs(result, error, cancelled);
this.asyncOperation.PostOperationCompleted(this.operationCompleted, arg);
}
protected virtual void OnDoWork(DoWorkEventArgs e)
{
DoWorkEventHandler handler = (DoWorkEventHandler) base.Events[doWorkKey];
if (handler != null)
{
handler(this, e);
}
}
There is no special handling to wait for async method. (using async/await keyword).
To make it wait for async operation, following changes are required.
async private void WorkerThreadStart(object argument)
await this.OnDoWork(e);
async protected virtual void OnDoWork(DoWorkEventArgs e)
await handler(this, e);
But then, BackgroundWorker is .net 2.0 construct, and async/await are .net 4.5. it will be full circle, if any one of these uses other construct.
You can't await an event handler because it doesn't return anything to await on. From the documentation of the async keyword:
The void return type is used primarily to define event handlers, where a void return type is required. The caller of a void-returning async method can't await it and can't catch exceptions that the method throws.
By adding the async keyword to the BgDoWork event handler you are instructing .NET to execute the handler asynchronously and return as soon as the first yielding operation is encountered. In this case, this happens after the first call to http.GetAsync