Button.PerformClick() 不等待异步事件处理程序

Pou*_*Bak 3 c# winforms async-await eventhandler

PerformClick我在用于调用事件处理程序的 Windows 窗体应用程序中遇到问题async。事件处理程序似乎没有,await而是立即返回。我创建了这个简单的应用程序来显示问题(它只是一个带有 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";
}
Run Code Online (Sandbox Code Playgroud)

message这里有趣的问题是,当我点击 时,你认为会显示什么Button2令人惊讶的是,它显示“开始”,而不是您所期望的“完成”。换句话说,它不会await MyMethod(),它只是启动任务,然后继续。

编辑:

在这个简单的代码中,我可以通过直接从 Button2 事件处理程序调用来使其工作await Method(),如下所示:

private async void button2_Click(object sender, EventArgs e)
{
    await MyMethodAsync();
    MessageBox.Show(message);
}
Run Code Online (Sandbox Code Playgroud)

现在,它会等待 2 秒并显示“已完成”。

这里发生了什么?为什么使用时不起作用PerformClick

结论:

好吧,现在我明白了,结论是:

  1. PerformClick如果事件处理程序是 ,则切勿调用async。它不会await

  2. 切勿async直接调用事件处理程序。它不会await

剩下的就是缺乏这方面的文档:

  1. Button.PerformClick文档页面上应该有一个警告:

Button.PerformClick调用 PerformClick 将不会等待异步事件处理程序。

  1. 调用async void方法(或事件处理程序)应该向编译器发出警告:“您正在调用 async void 方法,不会等待它!

Ahm*_*eed 6

async/await您似乎对如何工作和/或工作有一些误解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.
}
Run Code Online (Sandbox Code Playgroud)

现在,您希望 MessageBox 显示什么?你可能会说“开始”。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.
}
Run Code Online (Sandbox Code Playgroud)

现在,我所做的就是将异步方法内的代码移动MyMethodAsync()异步事件处理程序中button1_Click,然后使用 调用该事件处理程序button1_Click(null, null)。这和第一个代码有区别吗?不,本质上是一样的;在这两种情况下,我都调用了异步方法而不等待它2

如果到目前为止您同意我的观点,那么您可能已经理解为什么您的代码不能按预期工作。上面的代码(在第二种情况下)几乎与您的代码相同。不同之处在于我使用了button1_Click(null, null)代替button1.PerfomClick(),它本质上做了同样的事情。3

解决方案:

如果您想等待 中的代码button1_Click完成,则需要将其中的所有内容button1_Click (只要是异步代码)移至一个async方法中,然后在等待它。这正是您在“编辑”部分中所做的,但请注意,这也需要有签名。 button1_Clickbutton2_Clickbutton2_Clickasync


1 如果您认为答案是其他内容,那么您可能需要查看这篇文章,其中解释了该警告。

2 唯一的区别是,在第一种情况下,我们可以通过等待方法来解决问题,但是,在第二种情况下,我们不能这样做,因为“方法”是不可等待的,因为返回类型是void 即使它有async签名

3实际上,两者之间存在一些差异(例如,中的验证逻辑PerformClick()),但这些差异不会影响我们当前情况的最终结果。