最佳实践异步基本方法签名
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]
必须 等待(或获取结果)恰好一次。不是零次,不是两次:一次.
我想这个问题已经有一段时间了,但没有找到任何明确的答案。
我们正在使用 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]
必须 等待(或获取结果)恰好一次。不是零次,不是两次:一次.