为什么异步 void 被认为是不良实践?

28
所以我明白为什么从异步函数返回void通常没有意义,但我遇到了一种情况,我认为这是完全有效的。考虑下面这个人为的例子:
protected override void OnLoad(EventArgs e)
{
    if (CustomTask == null)
        // Do not await anything, let OnLoad return.
        PrimeCustomTask();
}
private TaskCompletionSource<int> CustomTask;

// I DO NOT care about the return value from this. So why is void bad?
private async void PrimeCustomTask()
{
    CustomTask = new TaskCompletionSource<int>();
    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);
}

我知道这个例子很不寻常,但我尽量让它简单且更通用。有人能解释一下为什么这段代码很糟糕,以及我应该如何修改它以正确遵循规范吗?
谢谢任何帮助。

1
阅读这个。https://msdn.microsoft.com/zh-cn/magazine/jj991977.aspx - Nkosi
@AnotherProgrammer,你无法从异步 void 中捕获错误,这不足以避免它们吗? - johnny 5
5
这似乎是一个 "XY 问题"。你试图达成的最终目标是什么?这个例子不太清楚。 - Nkosi
4
我建议提供一个[MCVE]来说明问题以及尝试了哪些解决方案,这样可以更好地理解问题并提供可行的解决方案。 - Nkosi
1
@AnotherProgrammer:不,async void 异常无法在 Main 中捕获。而 BackgroundWorker 没有同样的问题(尽管它有其他问题)。 - Stephen Cleary
显示剩余7条评论
2个回答

42

好的,根据“避免使用async void”文章中的原因:

  • async void方法具有不同的错误处理语义。从PrimeCustomTask中逃逸的异常将非常难以处理。
  • async void方法具有不同的组合语义。这是围绕代码可维护性和重用性的论点。基本上,PrimeCustomTask中的逻辑就在那里,不能组合到更高级别的async方法中。
  • async void方法很难测试。自然而然地跟随前两点,很难编写覆盖PrimeCustomTask(或调用它的任何内容)的单元测试。

另外需要注意的是,async Task才是自然的方法。在采用async/await几种语言中,我所知道的只有C#/VB支持async void。F#不支持,Python不支持,JavaScript和TypeScript也不支持。从语言设计的角度来看,async void是不自然的。

async void更改为用于异步事件处理程序的代码,这也是添加async void到C#/VB中的原因:

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

private async Task PrimeCustomTask()

那么async void的缺点仅限于您的事件处理程序。特别地,来自PrimeCustomTask的异常会自然地传播到其(异步)调用者(OnLoad),PrimeCustomTask可以被组合(从其他异步方法自然调用),并且PrimeCustomTask更容易包含在单元测试中。


30

使用void async通常被视为“不好”的原因有:

  • 你不能等待它完成(正如本篇文章已经提到的)
  • 如果被调用的父线程在它完成之前退出,这将尤为痛苦
  • 任何未处理的异常将终止您的进程(哎呀!)

像您这样的情况下,使用它是可以的。只要在使用时小心谨慎即可。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接