【问题标题】:Button.PerformClick() doesn't wait an async event hanlderButton.PerformClick() 不等待异步事件处理程序
【发布时间】:2018-09-03 21:57:18
【问题描述】:

我在使用PerformClick 调用async 事件处理程序的Windows 窗体应用程序中遇到问题。似乎事件处理程序不是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";
}

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

编辑:

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

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

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

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

结论:

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

  1. 如果事件处理程序是 async,则永远不要调用 PerformClick。不会await

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

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

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

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

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

【问题讨论】:

  • 召唤驱魔人!
  • 这就是重点,它不是在等待。
  • 不,它在 awaiting,它只是没有阻塞线程/UI,因此您可以继续单击按钮并使其等待多次你想要的。
  • 它按预期工作。当调用await MyMethodAsync(); 时,系统会检查MyMethodAsync 是否已经返回。它不是由于 Task.Delay(2000); 而导致的,因此它恢复到 button2_Click 并打印 "Started"。当然message = "Started"; 已经被执行了。
  • 这里没有发生异常情况。正确的思维模式是等待表达式之后之后的代码会稍后执行,就好像它是由计时器激活的一样。除了 ShowDialog() 和可怕的 Application.DoEvents() 之外,没有任何机制可以阻止 UI 线程上的方法。由于引起讨厌的重入错误的可能性很高,async/await 并没有解决这个根本问题。

标签: c# winforms async-await eventhandler


【解决方案1】:

您似乎对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 中的所有内容(只要是异步代码) 移动到async 方法中并然后 awaitboth button1_Clickbutton2_Click。这正是您在"Edit" section 中所做的,但请注意button2_Click 也需要有async 签名。


1如果您认为答案是其他内容,那么您可能需要查看this article,它解释了警告。

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

3其实两者还是有一些区别的(比如PerformClick()中的验证逻辑,但是这些区别不影响我们目前情况下的最终结果。

【讨论】:

  • 我在编辑的 'Button2' 事件处理程序中添加了 'async',这是一个错字。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-22
  • 1970-01-01
相关资源
最近更新 更多