在异步代码的Task或void方法之间进行选择

本文关键字:之间 方法 行选 选择 void 异步 代码 Task | 更新日期: 2023-09-27 17:50:58

我使用LoadState方法作为示例,但这通常可以被视为异步编程的场景。

LoadStateSaveState方法实现通常具有以下签名:

public override void LoadState(..)
public async override void LoadState(..)

您可以选择添加async关键字,这取决于您是否希望等待加载某些数据。但是由于LoadState的返回类型是void,LoadState方法本身是不能等待的,并且被触发为fire-and-forget。这通常是很好的,因为它允许响应UI,同时能够在数据加载逻辑中使用async/await。

在某些场景中,我希望等待异步LoadState方法。然而,如果我将签名更改为Task而不是void,那么我所有不使用异步逻辑的实现都必须返回null(这似乎不是最好的解决方案(。有没有另一种可能的解决方案,其中一些方法调用可以等待,而大多数方法调用保持原样并忘记(默认情况下(?

在异步代码的Task或void方法之间进行选择

async void方法存在以下几个问题:

  • 它不可堆肥;你不能await来完成它(正如你所指出的(
  • 它不容易测试(出于同样的原因(
  • 错误处理完全不同;我听过async void叫"火灾和遗忘",但我自己也用过"火灾和崩溃"这个词

此外,您永远不应该从Async方法返回null。这对于其他习惯于TAP的程序员来说是令人惊讶的。

您可以提供两种方法void LoadStateTask LoadStateAsync,但如果您这样做,我建议您始终提供这两种实现。有而不是一种将异步方法封装在同步方法中或将同步方法封装在异步方法中的简单方法,因此您最终会得到两个几乎相同的实现。

出于维护的原因,我不是特别喜欢这个。就我个人而言,我更喜欢一个可能的async方法,它总是有一个与await兼容的签名:

interface IWhatever
{
  Task LoadStateAsync();
}

实现应该是异步的,如果是同步的,则应该是快速的。同步实现可以使用Task.FromResult:

class Whatever : IWhatever
{
  public Task LoadStateAsync()
  {
    ... // fast-running synchronous code
    return Task.FromResult<object>(null);
  }
}

消耗代码将始终为async:

Whatever whatever = ...;
await whatever.LoadStateAsync();

请注意,如果实现是同步的,则消耗代码将不会屈服于其调用者;它将继续通过CCD_ 17同步运行。

我认为有一个Loaded事件,它在加载完成时被触发。也许您可以在Loaded事件的事件处理程序中执行您想继续执行的任何操作?

您可以将逻辑实现为Task返回方法,并让LoadState丢弃返回值。

public override void LoadState() { LoadStateInner(); }
Task LoadStateInner() { await ...; }

现在,您可以在同时履行继承契约的同时调用这两个变体。

我看到的一个可能的解决方案是在基类中定义两个方法(以及用于额外逻辑的第三个方法(:

virtual void LoadState(){}
virtual async Task LoadStateAsync{ }
virtual void SomeOtherLogic(){}

在调用LoadState的地方,现在有两个方法调用:

LoadState();
await LoadStateAsync();
SomeOtherLogic();

在此解决方案中,如果SometerLogic为空或不需要LoadState方法中的数据,则可以覆盖LoadState方法。如果确实需要数据,则在执行SomeOtherLogic之前等待LoadStateAsync方法。

不利的一面是,您(和其他开发人员(总是必须注意使用哪种方法。