最佳实践异步基本方法签名

Best practice async base method signature

我想这个问题已经有一段时间了,但没有找到任何明确的答案。

我们正在使用 Entity Framework (C#) 开发 WPF 应用程序。我们有一个通用基础 class 用于带有一些方法的列表组件,例如 LoadData。
目前它的签名是 abstract List<TItem> LoadData(),这意味着您不能在其中使用异步等待而不会失去对执行流程的控制(其他方法可能会在 LoadData 完成之前启动,从而造成竞争条件)。
Entity Framework 有一个扩展方法 ToListAsync() 可以异步获取数据。几乎每个组件都可以在 LoadData 中使用它。
因此,此方法非常适合重构为 abstract Task<List<TItem>> LoadDataAsync().

的签名

但是,在基础 class 上还有其他方法并不那么直接。大多数时候它们被覆盖,不需要异步。然而,有时(比如 1/20)如果方法 return Task/Task<T> 而不是 void/T 会非常有用。
每隔一段时间,这意味着我需要 return Task.CompletedTask/ 将方法的结果包装在 Task.FromResult(result).
中 这种方法的一个例子是 abstract TItem AddItem(someParameters),它在大多数情况下只是使用来自表单的参数创建一个新对象。但有时,您需要来自数据库或类似东西的额外数据。在这些情况下,您需要将基本方法的签名设置为 abstract Task<TItem> AddItemAsync(someParameters)。 (编辑:出于多种原因,这个想法真的很糟糕)

在性能不是大问题的情况下,是否有任何理由更喜欢没有 Task 的签名?这些方法每个模块只会调用一次,将 Task 添加到签名不会对性能产生很大影响。
另一个想法是同时提供普通基本方法和异步方法,但这会导致大量膨胀,可能只是一个坏主意。

TL;DR:有什么理由不在基本方法中使用 return Task/Task<T>(或 ValueTask<T>),即使 async 很少用于实际的实现和大部分时间我必须 return Task.CompletedTask/Task.FromResult(result)?

如果方法仅 很少 需要 async,但经常需要 return 值(因此 Task<T> 而不是 Task), 那么: ValueTask<T> 可能是你的朋友。与 Task<T> 不同,这不需要任何分配;例如:

public ValueTask<int> GetSomethingAsync(string key)
{
    if (cache.TryGetValue(key, out int value))
        return new(value); // or new ValueTask<int>(value) in older C#

    return SlowAsync(key);
}
private async ValueTask<int> GetSomethingAsync(string key)
{
    int value = ... await based code here
    cache[key] = value;
    return value;
}

但是,ValueTask[T] 有一个重要的语义变化:ValueTask[T] 必须 等待(或获取结果)恰好一次。不是零次,不是两次:一次.