【发布时间】:2022-09-23 21:05:18
【问题描述】:
据我所知,何时调用“async void”方法(例如事件处理程序),调用者永远无法知道它何时完成(因为它不能等待Task 完成) .所以有效地它是一个火灾和忘记电话。
用这段代码演示了这一点(我已经将一个 Button 和 TabControl 放在一个表单上并连接了 2 个事件)。单击按钮时,它会更改选项卡,这会导致引发 SelectedIndexChanged 事件,这是异步的。
private void button1_Click(object sender, EventArgs e)
{
Debug.WriteLine(\"Started button1_Click\");
tabControl1.SelectedTab = tabControl1.SelectedIndex == 0 ? tabPage2 : tabPage1;
Debug.WriteLine(\"Ended button1_Click\");
}
private async void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
Debug.WriteLine(\"Started tabControl1_SelectedIndexChanged\");
await Task.Delay(1000);
Debug.WriteLine(\"Ended tabControl1_SelectedIndexChanged\");
}
结果输出是
Started button1_Click
Started tabControl1_SelectedIndexChanged
Ended button1_Click
Ended tabControl1_SelectedIndexChanged
如您所见,SelectedIndexChanged 事件处理程序被触发,但调用者没有等待它完成(它不能等待,因为它没有要等待的任务)。
我提出的解决方案
事件处理程序不使用async,而是等待它使用的任何Async 方法,然后一切似乎都正常工作......它通过在调用DoEvents 时轮询Task.IsCompleted 属性来等待,以保持异步任务处于活动状态并且处理(在本例中为 Task.Delay)。
private void button1_Click(object sender, EventArgs e)
{
Debug.WriteLine(\"Started button1_Click\");
tabControl1.SelectedTab = tabControl1.SelectedIndex == 0 ? tabPage2 : tabPage1;
Debug.WriteLine(\"Ended button1_Click\");
}
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
Debug.WriteLine(\"Started tabControl1_SelectedIndexChanged\");
Await(Task.Delay(1000));
Debug.WriteLine(\"Ended tabControl1_SelectedIndexChanged\");
}
public static void Await(Task task)
{
while (task.IsCompleted == false)
{
System.Windows.Forms.Application.DoEvents();
}
if (task.IsFaulted && task.Exception != null)
throw task.Exception;
else
return;
}
这现在给出了预期的结果
Started button1_Click
Started tabControl1_SelectedIndexChanged
Ended tabControl1_SelectedIndexChanged
Ended button1_Click
任何人都可以看到采用这种方法的任何问题吗???
-
Application.DoEvents();被我们的团队认为是一个错误,不允许任何人签入此类代码。它允许重入调用,这可能会导致许多难以诊断的错误。而且您的解决方案也会阻塞,并且在某些情况下可能会引入死锁。所以不,不要那样做。 -
此外,通过强制代码等待,您已经阻止了 UI。关于
async/await的全部要点是您不要阻止用户界面! -
DoEvents 使 UI 保持活跃和处理,代码并没有真正阻塞,因为它仍在处理异步任务列表,就像您使用异步等待一样
-
The
DoEvents()is very likely to introduce bugs. 这里最大的问题是你为什么要这样做?你真正想解决什么问题? -
如果您在等待数据填充时确实必须阻止您的 UI,您应该查看this thread 以了解如何同步调用异步方法。
标签: c# winforms asynchronous async-await