为什么无效的异步不好?

所以我理解为什么从异步中返回void通常没有任何意义,但是我遇到了一种我认为完全有效的情况。 考虑以下人为的例子:

protected override void OnLoad(EventArgs e) { if (CustomTask == null) // Do not await anything, let OnLoad return. PrimeCustomTask(); } private TaskCompletionSource CustomTask; // I DO NOT care about the return value from this. So why is void bad? private async void PrimeCustomTask() { CustomTask = new TaskCompletionSource(); int result = 0; try { // Wait for button click to set the value, but do not block the UI. result = await CustomTask.Task; } catch { // Handle exceptions } CustomTask = null; // Show the value MessageBox.Show(result.ToString()); } private void button1_Click(object sender, EventArgs e) { if (CustomTask != null) CustomTask.SetResult(500); } 

我意识到这是一个不寻常的例子,但我试图让它更简单,更通用。 有人可以向我解释为什么这是可怕的代码,以及我如何修改它以正确遵循约定?

谢谢你的帮助。

好吧,通过“避免async void ”文章中的原因:

  • 异步void方法具有不同的error handling语义。 逃离PrimeCustomTaskexception处理起来非常尴尬。
  • 异步void方法具有不同的组合语义。 这是一个以代码可维护性和重用为中心的论据。 从本质上讲, PrimeCustomTask的逻辑就在那里,就是它 – 它不能组成更高级别的async方法。
  • 异步void方法很难测试。 自然从前两点开始,编写一个覆盖PrimeCustomTask (或任何调用它)的unit testing非常困难。

同样重要的是要注意async Task是自然的方法。 在采用async / await的几种语言中 ,C#/ VB是唯一支持async void的AFAIK。 F#没有,Python没有,JavaScript和TypeScript没有。 从语言设计的角度来看, async void是不自然的。

async void添加到C#/ VB的原因是为了启用异步事件处理程序。 如果更改代码以使用async void事件处理程序:

 protected override async void OnLoad(EventArgs e) { if (CustomTask == null) await PrimeCustomTask(); } private async Task PrimeCustomTask() 

然后async void缺点仅限于您的事件处理程序。 特别是,来自PrimeCustomTaskexception自然地传播到它的(异步)调用者( OnLoad ), PrimeCustomTask可以被组合(从其他异步方法自然地调用),并且PrimeCustomTask更容易包含在unit testing中。

使用void async通常只视为“坏”,因为:

  • 你不能等待它的完成(已在本文中提到)
  • 任何未处理的exception将终止您的过程(哎哟!)

有很多案例(比如你的)使用它很好。 使用它时要小心。