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."
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
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.