CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Ubuntu
Linux
Other
.NetMvc
VisualStudio
Git
pm
Python
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
如何使用 C# 中的 ValueTask
0
Csharp
小笨蛋
发布于:2021年12月25日
更新于:2021年12月25日
116
#custom-toc-container
> 原文链接:[《How to use ValueTask in C#》](https://www.infoworld.com/article/3565433/how-to-use-valuetask-in-csharp.html "《How to use ValueTask in C#》") ### 为什么要使用 ValueTask ? `Task` 表示某个操作的状态,即此操作是否完成、取消等。异步方法可以返回 `Task` 或者 `ValueTask`。 现在,由于 `Task` 是一个引用类型,从异步方法返回一个 `Task` 对象意味着每次调用该方法时都会在托管堆(`managed heap`)上分配该对象。因此,在使用 `Task` 时需要注意的一点是,每次从方法返回 Task 对象时都需要在托管堆中分配内存。如果你的方法执行的操作的结果立即可用或同步完成,则不需要这种分配,因此代价很高。 这正是 `ValueTask` 要出手相助的目的,`ValueTask
` 提供了两个主要好处。首先,`ValueTask
` 提高了性能,因为它不需要在堆(heap)中分配; 其次,它的实现既简单又灵活。当结果立即可用时,通过从异步方法返回 `ValueTask
` 代替 `Task
`,你可以避免不必要的分配开销,因为这里的 “T” 表示一个结构,而 C# 中的结构体(`struct`)是一个值类型(与 `Task
` 中表示类的 “T” 不同)。 C# 中 `Task` 和 `ValueTask` 表示两种主要的 “可等待(`awaitable`)” 类型。请注意,您不能阻塞(`block`)一个 `ValueTask`。如果需要阻塞,则应使用 `AsTask` 方法将 `ValueTask` 转换为 `Task`,然后在该引用 `Task` 对象上进行阻塞。 另外请**注意**,每个 `ValueTask` **只能被消费(`consumed`)一次**。这里的单词 “消费(`consume`)” 是指 `ValueTask` 可以异步等待(`await`)操作完成,或者利用 `AsTask` 将 `ValueTask` 转换为 `Task`。但是,`ValueTask` 只应被消费(`consumed`)一次,之后 `ValueTask
` 应被忽略。 ### C# 中的 ValueTask 示例 假设有一个异步方法返回一个 `Task`。你可以利用 `Task.FromResult` 创建 `Task` 对象,如下面给出的代码片段所示。 ```csharp public Task
GetCustomerIdAsync() { return Task.FromResult(1); } ``` 上面的代码片段并没有创建整个异步状态机制,但它在托管堆(`managed heap`)中分配了一个 `Task` 对象。为了避免这种分配,您可能希望利用 `ValueTask` 代替,像下面给出的代码片段所示的那样。 ```csharp public ValueTask
GetCustomerIdAsync() { return new ValueTask
(1); } ``` 下面的代码片段演示了 `ValueTask` 的同步实现。 ```csharp public interface IRepository
{ ValueTask
GetData(); } ``` `Repository` 类扩展了 `IRepository` 接口,并实现了如下所示的方法。 ```csharp public class Repository
: IRepository
{ public ValueTask
GetData() { var value = default(T); return new ValueTask
(value); } } ``` 下面是如何从 `Main` 方法调用 `GetData` 方法。 ```csharp static void Main(string[] args) { IRepository
repository = new Repository
(); var result = repository.GetData(); if (result.IsCompleted) Console.WriteLine("Operation complete..."); else Console.WriteLine("Operation incomplete..."); Console.ReadKey(); } ``` 现在让我们将另一个方法添加到我们的存储库(`repository`)中,这次是一个名为 `GetDataAsync` 的异步方法。以下是修改后的 `IRepository` 接口的样子。 ```csharp public interface IRepository
{ ValueTask
GetData(); ValueTask
GetDataAsync(); } ``` `GetDataAsync` 方法由 `Repository` 类实现,如下面给出的代码片段所示。 ```csharp public class Repository
: IRepository
{ public ValueTask
GetData() { var value = default(T); return new ValueTask
(value); } public async ValueTask
GetDataAsync() { var value = default(T); await Task.Delay(100); return value; } } ``` ### C# 中应该在什么时候使用 ValueTask ? 尽管 `ValueTask` 提供了一些好处,但是使用 `ValueTask` 代替 `Task` 有一定的权衡。`ValueTask` 是具**有两个字段的值类型**,而 `Task` 是具有**单个字段的引用类型**。因此,使用 `ValueTask` 意味着要处理更多的数据,因为方法调用将返回两个数据字段而不是一个。另外,如果您等待(`await`)一个返回 `ValueTask` 的方法,那么该异步方法的状态机也会更大,因为它必须容纳一个包含两个字段的结构体而不是在使用 `Task` 时的单个引用。 此外,如果异步方法的使用者使用 `Task.WhenAll` 或者 `Task.WhenAny`,在异步方法中使用 `ValueTask
` 作为返回类型可能会代价很高。这是因为您需要使用 `AsTask` 方法将 `ValueTask
` 转换为 `Task
`,这会引发一个分配,而如果使用起初缓存的 `Task
`,则可以轻松避免这种分配。 **经验法则是这样的:当您有一段代码总是异步的时候,即当操作(总是)不能立即完成时,请使用 `Task`。当异步操作的结果已经可用时,或者当您已经缓存了结果时,请利用 `ValueTask`。不管怎样,在考虑使用 `ValueTask` 之前,您都应该执行必要的性能分析。** PS:如果你的方法是**半个异步的**,半个异步的方法是指如果我们调用方法时命中了缓存,那就会直接创建一个 `Task` 对象并返回,在访问量大的情况下是很消耗托管堆(`managed heap`)空间的,这时就可以考虑使用 `ValueTask` 来代替 `Task`。 ![图片alt](/uploads/images/20211225/131221-1d2a7b9fcfee402ebc9b0ac1cf4073ce.png ''代码片段:Www.CodeSnippet.Cn'') 下图是使用`Benchmark`做的性能分析,可以看到在执行时间上两种返回值几乎无差别,但在内存使用率上就... ![图片alt](/uploads/images/20211225/131019-ce22defc467c43e58d9db66ba29644fc.png ''代码片段:Www.CodeSnippet.Cn'')
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2024