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

时间:2018-09-03 21:57:18

标签: c# winforms async-await eventhandler

我在Windows Forms应用程序中遇到问题,该应用程序使用PerformClick来调用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";
}

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

编辑:

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

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

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

这是怎么回事?为什么在使用PerformClick时不起作用?

结论:

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

  1. 如果事件处理程序为PerformClick,请不要调用async。不会await

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

剩下的是缺少相关文档:

  1. Button.PerformClick在文档页面上应具有警告

Button.PerformClick呼叫PerformClick不会等待异步事件处理程序。

  1. 调用async void方法(或事件处理程序)应给予编译器警告:“ 您正在调用异步void方法,将不会等待它!

1 个答案:

答案 0 :(得分:4)

您似乎对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.
}
  

注意:编译器会向我们发出警告,但为了进行测试,我们将其忽略。

现在,您希望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.
}

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

到目前为止,如果您同意我的意见,您可能已经了解了为什么您的代码无法按预期工作。上面的代码(在第二种情况下)与您的代码几乎相同。区别在于我使用的是button1_Click(null, null)而不是button1.PerfomClick(),它实际上是做同样的事情。 3

解决方案:

如果您要等待button1_Click中的代码完成,则需要将button1_Click (只要是异步代码)中的所有内容移到{ {1}}方法,然后在两者 asyncbutton1_Click等待。这正是您在"Edit" section中所做的,但是请注意,button2_Click也需要具有button2_Click签名。


1 如果您认为答案是另外的话,那么您可能需要检查this article来解释警告。

2 唯一的区别是,在第一种情况下,我们可以通过等待该方法来解决问题,但是,在第二种情况下,我们不能这样做,因为“方法” because the return type is void ,尽管它具有async签名

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