Button.PerformClick() 不等待异步事件处理程序
Button.PerformClick() doesn't wait an async event hanlder
我在使用 PerformClick
调用 async
事件处理程序的 Windows Forms 应用程序中遇到问题。事件处理程序似乎没有 await
,而是立即 returns。我已经创建了这个简单的应用程序来显示问题(它只是一个带有 3 个按钮的表单,很容易测试你自己):
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";
}
这里有趣的问题是,当我单击 Button2
时,您认为 message
显示什么?
令人惊讶的是, 它显示 "Started",而不是您期望的 "Finished"。换句话说,它不会 await MyMethod()
,它只是启动任务,然后继续。
编辑:
在这个简单的代码中,我可以通过直接从 Button2 事件处理程序调用 await Method()
来使其工作,如下所示:
private async void button2_Click(object sender, EventArgs e)
{
await MyMethodAsync();
MessageBox.Show(message);
}
现在,它等待 2 秒并显示 'Finished'。
这是怎么回事?为什么在使用 PerformClick
时不起作用?
结论:
好了,现在明白了,结论是:
如果事件处理程序是 async
,则永远不要调用 PerformClick
。不会await
!
切勿直接调用 async
事件处理程序。不会await
!
剩下的就是缺少这方面的文档:
Button.PerformClick
应该在文档页面上有一个 警告:
Button.PerformClick
“调用 PerformClick 不会等待异步事件处理程序。”
- 调用
async void
方法(或事件处理程序)应该给编译器 警告:“你正在调用一个 async void 方法,它将不用等了!"
您似乎对 async/await
and/or PerformClick()
的工作方式有一些误解。为了说明问题,请考虑以下代码:
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.
}
Note: the compiler will give us a warning but let's ignore that for the sake of testing.
现在,您希望 MessageBox 显示什么?您可能会说 "started".1 为什么?因为我们没有等待 MyMethodAsync()
方法;该方法中的代码运行异步但我们没有等待它完成,我们只是继续到下一行message
尚未更改。
如果您了解到目前为止的行为,剩下的应该很容易。所以,让我们稍微改变一下上面的代码:
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.
}
现在,我所做的就是将 async 方法 MyMethodAsync()
中的代码移动到 async事件处理程序 button1_Click
,然后我使用 button1_Click(null, null)
调用该事件处理程序。这个和第一个代码有区别吗?不,本质上是一样的;在这两种情况下,我都调用了异步方法 而没有等待它 .2
如果到目前为止您同意我的观点,那么您可能已经理解为什么您的代码无法按预期工作。上面的代码(在第二种情况下)与您的代码几乎相同。不同的是,我使用 button1_Click(null, null)
而不是 button1.PerfomClick()
,两者本质上做同样的事情。3
解决方法:
如果要等待button1_Click
中的代码执行完毕,则需要将button1_Click
里面的所有内容都移动(只要是异步代码) 进入 async
方法,然后 await 它在 both button1_Click
和 button2_Click
中。这正是您在 "Edit" section 中所做的,但请注意 button2_Click
也需要有一个 async
签名。
1 如果您认为答案是别的,那么您可能需要查看 this article 来解释警告。
2 唯一不同的是,第一种情况,我们可以通过等待方法来解决问题,而第二种情况,我们可以不要那样做,因为 "method" 不可等待 because the return type is void
即使它具有 async
签名。
3其实两者还是有些区别的(比如PerformClick()
中的验证逻辑,但是这些区别不影响最后的结果就是我们现在的情况。
我在使用 PerformClick
调用 async
事件处理程序的 Windows Forms 应用程序中遇到问题。事件处理程序似乎没有 await
,而是立即 returns。我已经创建了这个简单的应用程序来显示问题(它只是一个带有 3 个按钮的表单,很容易测试你自己):
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";
}
这里有趣的问题是,当我单击 Button2
时,您认为 message
显示什么?
令人惊讶的是, 它显示 "Started",而不是您期望的 "Finished"。换句话说,它不会 await MyMethod()
,它只是启动任务,然后继续。
编辑:
在这个简单的代码中,我可以通过直接从 Button2 事件处理程序调用 await Method()
来使其工作,如下所示:
private async void button2_Click(object sender, EventArgs e)
{
await MyMethodAsync();
MessageBox.Show(message);
}
现在,它等待 2 秒并显示 'Finished'。
这是怎么回事?为什么在使用 PerformClick
时不起作用?
结论:
好了,现在明白了,结论是:
如果事件处理程序是
async
,则永远不要调用PerformClick
。不会await
!切勿直接调用
async
事件处理程序。不会await
!
剩下的就是缺少这方面的文档:
Button.PerformClick
应该在文档页面上有一个 警告:
Button.PerformClick “调用 PerformClick 不会等待异步事件处理程序。”
- 调用
async void
方法(或事件处理程序)应该给编译器 警告:“你正在调用一个 async void 方法,它将不用等了!"
您似乎对 async/await
and/or PerformClick()
的工作方式有一些误解。为了说明问题,请考虑以下代码:
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.
}
Note: the compiler will give us a warning but let's ignore that for the sake of testing.
现在,您希望 MessageBox 显示什么?您可能会说 "started".1 为什么?因为我们没有等待 MyMethodAsync()
方法;该方法中的代码运行异步但我们没有等待它完成,我们只是继续到下一行message
尚未更改。
如果您了解到目前为止的行为,剩下的应该很容易。所以,让我们稍微改变一下上面的代码:
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.
}
现在,我所做的就是将 async 方法 MyMethodAsync()
中的代码移动到 async事件处理程序 button1_Click
,然后我使用 button1_Click(null, null)
调用该事件处理程序。这个和第一个代码有区别吗?不,本质上是一样的;在这两种情况下,我都调用了异步方法 而没有等待它 .2
如果到目前为止您同意我的观点,那么您可能已经理解为什么您的代码无法按预期工作。上面的代码(在第二种情况下)与您的代码几乎相同。不同的是,我使用 button1_Click(null, null)
而不是 button1.PerfomClick()
,两者本质上做同样的事情。3
解决方法:
如果要等待button1_Click
中的代码执行完毕,则需要将button1_Click
里面的所有内容都移动(只要是异步代码) 进入 async
方法,然后 await 它在 both button1_Click
和 button2_Click
中。这正是您在 "Edit" section 中所做的,但请注意 button2_Click
也需要有一个 async
签名。
1 如果您认为答案是别的,那么您可能需要查看 this article 来解释警告。
2 唯一不同的是,第一种情况,我们可以通过等待方法来解决问题,而第二种情况,我们可以不要那样做,因为 "method" 不可等待 because the return type is void
即使它具有 async
签名。
3其实两者还是有些区别的(比如PerformClick()
中的验证逻辑,但是这些区别不影响最后的结果就是我们现在的情况。