ValueTask 避免从异步方法返回
Task 对象时分配翻译自 Joydip Kanjilal 2020年7月6日 的文章 《How to use ValueTask in C#》
async 和
await 关键字的引入,它变得更加强大。您可以利用异步编程来提高应用程序的响应能力和吞吐量。Task。如果您想编写一个有返回值的异步方法,那么应该返回
Task<T>; 如果想编写事件处理程序,则可以返回
void。在 C# 7.0 之前,异步方法可以返回
Task、
Task<T> 或
void。从 C# 7.0 开始,异步方法还可以返回
ValueTask(作为
System.Threading.Tasks.Extensions 包的一部分可用)或
ValueTask<T>。本文就讨论一下如何在 C# 中使用
ValueTask。ValueTask 的用法。Task 表示某个操作的状态,即此操作是否完成、取消等。异步方法可以返回
Task 或者
ValueTask。Task 是一个引用类型,从异步方法返回一个
Task 对象意味着每次调用该方法时都会在托管堆(
managed heap)上分配该对象。因此,在使用
Task 时需要注意的一点是,每次从方法返回
Task 对象时都需要在托管堆中分配内存。如果你的方法执行的操作的结果立即可用或同步完成,则不需要这种分配,因此代价很高。ValueTask 要出手相助的目的,
ValueTask<T> 提供了两个主要好处。首先,
ValueTask<T> 提高了性能,因为它不需要在堆(
heap)中分配; 其次,它的实现既简单又灵活。当结果立即可用时,通过从异步方法返回
ValueTask<T> 代替
Task<T>,你可以避免不必要的分配开销,因为这里的 “T” 表示一个结构,而 C# 中的结构体(
struct)是一个值类型(与
Task<T> 中表示类的 “T” 不同)。Task 和
ValueTask 表示两种主要的 “可等待(awaitable)” 类型。请注意,您不能阻塞(block)一个
ValueTask。如果需要阻塞,则应使用
AsTask 方法将
ValueTask 转换为
Task,然后在该引用
Task 对象上进行阻塞。ValueTask 只能被消费(consumed)一次。这里的单词 “消费(consume)” 是指
ValueTask 可以异步等待(
await)操作完成,或者利用
AsTask 将
ValueTask 转换为
Task。但是,
ValueTask 只应被消费(consumed)一次,之后
ValueTask<T> 应被忽略。Task。你可以利用
Task.FromResult 创建
Task 对象,如下面给出的代码片段所示。public Task<int> GetCustomerIdAsync() { return Task.FromResult(1); }
managed heap)中分配了一个
Task 对象。为了避免这种分配,您可能希望利用
ValueTask 代替,像下面给出的代码片段所示的那样。public ValueTask<int> GetCustomerIdAsync() { return new ValueTask<int>(1); }
ValueTask 的同步实现。public interface IRepository<T> { ValueTask<T> GetData(); }
Repository 类扩展了
IRepository 接口,并实现了如下所示的方法。public class Repository<T> : IRepository<T> { public ValueTask<T> GetData() { var value = default(T); return new ValueTask<T>(value); } }
Main 方法调用
GetData 方法。static void Main(string[] args) { IRepository<int> repository = new Repository<int>(); var result = repository.GetData(); if (result.IsCompleted) Console.WriteLine("Operation complete..."); else Console.WriteLine("Operation incomplete..."); Console.ReadKey(); }
GetDataAsync 的异步方法。以下是修改后的
IRepository 接口的样子。public interface IRepository<T> { ValueTask<T> GetData(); ValueTask<T> GetDataAsync(); }
GetDataAsync 方法由
Repository 类实现,如下面给出的代码片段所示。public class Repository<T> : IRepository<T> { public ValueTask<T> GetData() { var value = default(T); return new ValueTask<T>(value); } public async ValueTask<T> GetDataAsync() { var value = default(T); await Task.Delay(100); return value; } }
ValueTask 提供了一些好处,但是使用
ValueTask 代替
Task 有一定的权衡。
ValueTask 是具有两个字段的值类型,而
Task 是具有单个字段的引用类型。因此,使用
ValueTask 意味着要处理更多的数据,因为方法调用将返回两个数据字段而不是一个。另外,如果您等待(
await)一个返回
ValueTask 的方法,那么该异步方法的状态机也会更大,因为它必须容纳一个包含两个字段的结构体而不是在使用
Task 时的单个引用。Task.WhenAll 或者
Task.WhenAny,在异步方法中使用
ValueTask<T> 作为返回类型可能会代价很高。这是因为您需要使用
AsTask 方法将
ValueTask<T> 转换为
Task<T>,这会引发一个分配,而如果使用起初缓存的
Task<T>,则可以轻松避免这种分配。Task。当异步操作的结果已经可用时,或者当您已经缓存了结果时,请利用
ValueTask。不管怎样,在考虑使用
ValueTask 之前,您都应该执行必要的性能分析。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69983768/viewspace-2723774/ ,如需转载,请注明出处,否则将追究法律责任。