【问题标题】:Why exactly is void async bad?为什么 void async 到底是坏的?
【发布时间】:2017-08-01 21:28:32
【问题描述】:

所以我明白为什么从异步返回 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);
}

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

感谢您的帮助。

【问题讨论】:

  • @Nkosi 我明白为什么这是一种不好的做法,但为什么我的代码特别是这些坏做法的症状?如果不使用 void async,我还能怎么做?
  • @AnotherProgrammer 这似乎是XY problem。你想要达到的最终目标是什么?这个例子不清楚。
  • @AnotherProgrammer 我建议提供问题的minimal reproducible example 以及尝试了哪些解决方案,以便更好地理解问题并提供可行的解决方案。
  • @AnotherProgrammer: 不,async void 异常不能Main 捕获。而BackgroundWorker 则没有同样的问题(虽然还有其他问题)。

标签: c# async-await


【解决方案1】:

好了,在"avoid async void" article里走一走原因:

  • Async void 方法具有不同的错误处理语义。从PrimeCustomTask 转义的异常将很难处理。
  • 异步 void 方法具有不同的组合语义。这是一个围绕代码可维护性和重用性的争论。从本质上讲,PrimeCustomTask 中的逻辑就在那里,仅此而已 - 它不能组合成更高级别的 async 方法。
  • 异步 void 方法很难测试。从前两点自然出发,编写一个涵盖PrimeCustomTask(或任何调用它的东西)的单元测试是非常困难的。

同样重要的是要注意async Task 是自然的方法。在several languages that have adopted 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 的缺点仅限于您的事件处理程序。特别是,PrimeCustomTask 的异常会自然地传播给它的(异步)调用者(OnLoad),PrimeCustomTask 可以组合(从其他异步方法自然调用),PrimeCustomTask 更容易包含在一个单元中测试。

【讨论】:

    【解决方案2】:

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

    • 您不能等待它完成(如本文所述
    • 如果它被一个在它完成之前退出的父线程调用,尤其痛苦
    • 任何未处理的异常都会终止您的进程(哎哟!)

    在很多情况下(如您的情况)使用它都很好。请谨慎使用。

    【讨论】:

    • 我真的很喜欢这个答案,因为它摒弃了许多“编码指南”通常的绝对主义,而是重申了普遍的智慧(如果有点过于简洁),但也肯定了完全有效的和正确的用例。
    猜你喜欢
    • 1970-01-01
    • 2015-05-29
    • 2011-02-04
    • 1970-01-01
    • 2021-10-31
    • 2018-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多