百度推广需要自己有网站吗,seo基础视频教程,网站续费文档,直播教育网站建设.NET Framework 4 里面的命名空间为 System.Threading.Tasks的 Task 类。这个类以及它派生的 TaskTResult 早已成为编程的主要部分#xff0c;在 C#5 中的异步编程模式当作介绍了 async/await。在这篇文章里#xff0c;我会覆盖新的类 ValueTask / ValueTaskTRes… .NET Framework 4 里面的命名空间为 System.Threading.Tasks的 Task 类。这个类以及它派生的 TaskTResult 早已成为编程的主要部分在 C#5 中的异步编程模式当作介绍了 async/await。在这篇文章里我会覆盖新的类 ValueTask / ValueTaskTResult介绍它们在通用的使用上降低内存消耗来提高异步性能这是非常重要的。TaskTask 有多种用途但是它的核心就是 “promise”它表示最终完成的一些操作。你初始化一个操作并返回给它一个 Task它当操作完成的时候它会完成这可能作为初始化操作的一部分同步发生比如访问一个早就缓冲好了的缓冲区也有能是异步的在你完成这个任务时比如访问一些还没有缓冲好的字节但是很快就缓冲好了可以访问或者是在你已经接收 Task 的时候异步完成比如通过网络访问数据。因为操作完成可能是异步的所以你需要为结果等待它但这经常违背异步编程的初衷或者你必须提供一个回调函数来调用当这个操作完成的时候。在 .NET 4 中提供了如回调函数一样的来实现如 Task.ContinueWith 方法它暴露通过传递一个委托的回调函数这个函数在 Task 完成时触发SomeOperationAsync().ContinueWith(task {try {TResult result task.Result;UseResult(result);} catch (Exception e) {HandleException(e);}
})但是在 C# 5 以及 .NET Framwrok 4.5 中Task 只需要 await 这样就能很简单的获取这个异步操作完成返回的结果它生成的代码能够优化上述所有情况无论操作是否同步完成是否异步完成还是在已经提供的回调异步完成都可以正确地处理事情。TResult result await SomeOperationAsync();
UseResult(result);Task 很灵活并且有很多好处。例如你可以通过多个消费者并行等待多次。你可以存储一个到字典中以便后面的任意数量的消费者等待它允许为异步结果像缓存一样使用。如果场景需要你可以阻塞一个等待完成。并且你可以在这些任务上编写和使用各种操作就像组合器例如 WhenAny 操作它可以异步等待第一个操作完成。然而这种灵活性对于大多数情况下是不需要的仅仅只是调用异步操作并且等待结果TResult result await SomeOperationAsync();
UseResult(result);如上用法我们根本不需要多次等待我们不需要处理并行等待我们也不需要处理异步阻塞我们更不需要去写组合器。我们只是简单的等待异步操作 promise 返回的结果。这就是全部我们怎么写异步代码例如 TResult SomeOperation();也能很自然而然的用 async / await。进一步说Task 会有潜在的副作用在特定的场景中这个例子被大量创建并且高吞吐和高性能为关键概念Task 是一个类。作为一个类就是说任意操作创建一个 Task 都会分配一个对象越来越多的对象都会被分配所以 GC 操作也会越来越频繁也就会消耗越来越多的资源本来它应该是去做其他事的。运行时和核心库能减轻大多数这种场景。举个例子如果你写了如下代码public async Task WriteAsync(byte value)
{if(_bufferedCount _buffer.Length){await FlushAsync();}_buffer[_bufferedCount] value;
}在常规的例子中缓冲区有可用空间并且操作是同步完成。当这样运行的时候这里返回的 Task 没有任何特别之处因为它不会返回值这个返回 Task 就等价于返回一个 void 的同步方法。尽管如此运行时能够简单缓存单个非泛型的 Task 以及对于所有的 async Task 同步完成的方法都能重复使用它暴露的缓存的单例就像是 Task.CompletedTask。例如你的代码可以这样public async Taskbool MoveNextAsync()
{if(_bufferedCount 0){await FillBuffer();}return _bufferedCount 0;
}通常情况下我们期望会有一些数据被缓冲在这个例子中这个方法检查 _bufferedCount检验值大于 0并返回 true只有当前缓冲区域没有缓冲数据时它才需要执行可能是异步完成的操作。由于这里是 Boolean 存在两个可能的结果true 和 false这里可能只有两个对象 Taskbool它需要表示所有可能的结果值所以运行时会缓存两个对象以及简单返回一个 Taskbool 的缓存对象它的结果值为 true 来避免必要的分配。只有当操作异步完成时这个方法需要分配一个新的 Taskbool因为在它知道这个操作的结果之前它需要将对象返回给调用者并且还要必须有一个唯一的对象当操作完成的时候将它存进去。运行时为其他的类型很好的维护一个很小的缓存但是用它来存储所有是行不通的。例如下面方法public async Taskint ReadNextByteAsync()
{if(_bufferedCount 0){await FillBuffer();}if(_bufferedCount 0) {return -1;}_bufferedCount--;return _buffer[_pisition];
}也经常会同步完成。但是不像 Boolean 那个例子这个方法返回一个 Int32 的值它大约有 40 亿中可能的值并且为所有的情况缓存一个 Taskint将会消耗可能数以百计的千兆字节内存。运行时为 Taskint 负责维护一个小的缓存但是只有很小部分结果的值有用到例如如果是同步完成的数据缓存到缓存区返回值如 4它最后使用了缓存 task但是如果这个操作是同步完成返回结果值如 42它最后将分配一个新的 Taskint就类似调用 Task.FromResult(42)。很多库实现了尝试通过维护它们自己的缓存来降低这个特性。例如 .NET Framwork 4.5 的 MemoryStream.ReadAsync 重载函数总是同步完成的因为它只是从内存中读数据。ReadAsync 返回一个 Taskint这个 Int32 结果值表示读的字节数。ReadAsync 经常用在循环中表示每次调用请求的字节数ReadAsync 能够完全满足这个请求。因此通常情况下的请求重复调用 ReadAsync 来同步返回一个 Taskint其结果与之前的调用相同。因此MemoryStream 维护单个 task 的缓存它成功返回最后一个 task。然后再接着调用如果这个新的结果与缓存的 Taskint 匹配它只返回已经缓存的。否则它会使用 Task.FromResult 来创建一个新的存储到新的缓存 task 并返回。即使如此还有很多案例这些操作同步完成并且强制分配一个 TaskTResult 返回。ValueTaskTResult 和同步完成所有的这些都引发 .NET CORE 2.0 引入了一个新类型可用于之前的 .NET 版本 在System.Threading.Tasks.Extensions Nuget 包中ValueTaskTResult。ValueTaskTResult 在 .NET Core 2.0 作为结构体引入的它是 TResult 或 TaskTResult 包装器。也就是说它能从异步方法返回并且如果这个方法同步成功完成不需要分配任何内存我们只是简单的初始化这个 ValueTaskTResult 结构体它返回 TResult。只有当这个方法异步完成时TaskResult 才需要被分配通过 被创建的ValueTaskTResult 来包装这个实力对象为了最小化的大小的 ValueTaskTResult 以及优化成功路径一个异步方法它出现故障并且出现未处理的异常它还是会分配一个 TaskTResult 对象所以 ValueTaskTResult能简单的包装 TaskTResult 而不是必须添加额外的字段来存储异常信息。于是像 MemoryStream.ReadAsync 这个方法它返回一个 ValueTaskint它没有缓存的概念并且能像下面代码一样编码public override ValueTaskint ReadAsync(byte[] buffer, int offset, int count)
{try {int butyRead Read(buffer, offset, count);return new ValueTaskint(bytesRead);}catch (Exception e){return new ValueTaskint(Task.FromExceptionint(e));}
}ValueTask 和 异步完成为了写一个异步方法不需要为结果类型占用额外的分配的情况下完成同步是一个巨大的胜利。这就是为什么我们把 ValueTaskTResult 添加到 .NET Core 2.0 的原因以及为什么我们期望去使用的新的方法返回 ValueTaskTResult 而不是 TaskTResult。例如当我们添加新的 ReadAsync 重载函数到 Stream 类中是为了能够传递给 Memorybyte 而不是 byte[]我们使它返回的类型是 ValueTaskTResult。这样Stream它提供 ReadAsync 同步完成方法和之前的 MemoryStream 的例子一样使用这个签名ValueTask能够减少内存分配。然而工作在高吞吐的服务时我们还是要考虑尽可能的减少分配也就是说要考虑减少以及移除异步完成相关的内存分配。对于 await 模式对于所有的异步完成的操作我们都需要能够去处理返回表示完成事件的操作的对象调用者必须能够传递当操作完成时要被调用的回调函数以及要求有一个唯一对象能够被重用这需要有一个唯一的对象在堆上它能够作为特定操作的管道。但是这并不以为这一旦这个操作完成所有关于这个对象都能被重用。如果这个对象能够被重用那么这个 API 维护一个或多个这样对象的缓存并且为序列化操作重用这意思就是说不能使用相同对象到多次异步操作但是对于非并发访问是可以重用的。在 .NET Core 2.1ValueTaskTResult 增强功能支持池化和重用。而不只是包装 TResult 或 TaskTResulty引入了一个新的接口IValueTaskSourceTResult增强 ValueTaskTResult 能够包装的很好。IValueTaskSourceTResult 提供必要的核心的支持以类似于 TaskTResult 的方式来表示 ValueTaskTResult 的异步操作public interface IValueTaskSourceout TResult
{ValueTaskSourceStatus GetStatus(short token);void OnCompleted(Actionobject continuation, object state, short token, ValueTaskSourceOmCompletedFlags flags);TResult GetResult(short token);
}GetStatus 用来满足像 ValueTaskTResult.Completed 等属性返回一个表示异步操作是否正在执行中还是是否完成还是怎么样成功或失败。OnCompleted 是被 ValueTaskTResult 的可等待者用于当操作完成时从 await 中挂起的回调函数继续运行。GetResult 用于获取操作的结果就像操作完成后等待着能够获得 TResult 或传播可能发生的所有异常。绝大多数开发者不需要去看这个接口方法简单返回一个 ValueTaskTResult它被构造去包装这个接口的实例消费者并不知情consumer is none-the-wiser。这个接口主要就是让开发者关注性能的 API 能够避免内存分配。在 .NET Core 2.1 有一些这样的 API。最值得注意的是 Socket.ReceiveAsync 和 Socket.SendAsync有新增的重载例如public ValueTaskint ReceiveAsync(Momorybyte buffer, SocketFlags socketFlags, CancellationToken cancellationToken default);这个重载返回 ValueTaskint。如果这个操作同步 完成它能构造一个 ValueTaskint 并得到一个合适的结果。int result ...;
return new ValueTaskint(result);Socket 实现了维护一个用于接收和一个用来发送的池对象这样每次每个完成的对象不超过一个这些重载函数都是 0 分配的甚至是它们完成了异步操作。然后 NetworkStream 就出现了。举个例子在 .NET Core 2.1 中 Stream 暴露这样一个方法public virtual ValueTaskint ReadAsync(Memorybyte buffer, cancellationToken cancellationToken);这个复写方法 NetworkStream。NetworkStream.ReadAsync 只是委托给 Socket.ReceiveAsync所以从 Socket 转成 NetworkStream并且 NetworkStream.ReadAsync 是高效的无分配的。非泛型 ValueTask当 .NET Core 2.0 引入 ValueTaskTResult 它纯碎是为了优化同步完成的情况下为了避免分配一个 TaskTResult 存储可用的 TResult。这也就是说非泛型的 ValueTask 是不必要的对于同步完成的情况从 Task 返回的方法返回 Task.CompletedTask 单例并且为 async Task 方法在运行时隐式的返回。随着异步方法零开销的实现非泛型 ValueTask 变得再次重要起来。因此在 .NET Core 2.1 中我们也引入了非泛型的 ValueTask 和 IValueTaskSource。它们提供泛型的副本版本相同方式使用在 void 类型使用。IValueTaskSource / IValueTaskSource 实现大多数开发者不需要实现这些接口。它们也不是那么容易实现的。如果你需要这么做在 .NET Core 2.1 有一些内部实现作为参考。例如AwaitableSocketAsyncEventArgsAsyncOperationDefaultPipeReader为了让开发者想做的更加简单在 .NET Core 3.0 中我们计划引入所有封装这些逻辑到 ManualResetValueTaskSourceTResult 类中去这是一个结构体能被封装到另一个对象中这个对象实现了 IValueTaskSourceTResult 以及/或者 IValueTaskSource这个包装类简单的将大部分实现委托给结构体即可。要了解更多相关的问题详见 dotnet/corefx 仓库中的 issues。ValueTasks 有效的消费模式从表面上来看ValueTask 和 ValueTaskTResult 要比 Task 和 TaskTResult 更加有限。没错这个方法主要的消费就是简单的等待它们。但是因为 ValueTask 和 ValueTaskTResult 可能封装可重用的对象因此与 Task 和 TaskTResult 相比如果有人偏离期望的路径而只是等待它们它们的消耗实际上受到了很大的限制。一般的像下面的操作永远不会执行在 ValueTask / ValueTaskTResult 上等待 ValueTask / ValueTaskTResult 多次。底层对象可能已经回收了并被其他操作使用。与之对比Task / TaskTResult 将永不会把从完成状态转成未完成状态所以你能根据需要等待多次并每次总是能得到相同的结果。并发等待 ValueTask / ValueTaskTResult。底层对象期望一次只在从单个消费者的回调函数执行如果同时等待它很容易发生条件争用以及微妙的程序错误。这也是上述操作具体的例子“等待 ValueTask / ValueTaskTResult 多次。”相反Task / TaskTResult 支持任何数量并发的等待。当操作还没完成时调用 .GetAwaiter().GetResult()。IValueTaskSource / IValueTaskSourceTResult 的实现在操作还没完成之前是不需要支持阻塞的并且很可能不会这样的操作本质上就是条件争用不太可能按照调用者的意图调用。相反Task / TaskTResult 能够这样做阻塞调用者一直到任务完成。如果你在使用 ValueTask / ValueTaskTResult 以及你需要去做上述提到的你应该使用它的 .AsTask() 方法获得 Task / TaskTResult然后方法会返回一个 Task 对象。在那之后你就不能再次使用 ValueTask / ValueTaskTResult。简而言之对于 ValueTask / ValueTaskTResult你应该要么直接 await 可选 .ConfigureAwait(false)要么调用直接调用 AsTask()并且不会再次使用它了。
public ValueTaskint SomeValueTaskReturningMethodAsync();
...int result await SomeValueTaskReturningMethodAsync();int result await SomeValueTaskReturningMethodAsync().ConfigureAwait(false);Taskint t SomeValueTaskReturningMethodAsync().AsTask();ValueTaskint vt SomeValueTaskReturningMethodAsync();
...ValueTaskint vt SomeValueTaskReturningMethodAsync();
int result await vt;
int result2 await vt;ValueTaskint vt SomeValueTaskReturningMethodAsync();
Task.Run(async () await vt);
Task.Run(async () await vt);ValueTaskint vt SomeValueTaskReturningMethodAsync();
int result vt.GetAwaiter().GetResult();还有一个高级模式开发者可以选择使用在自己衡量以及能找到它提供的好处才使用它。特别的ValueTask / ValueTaskTResult 提供了一些属性他们能表明当前操作的状态例如如果操作还没完成 IsCompleted 属性返回 false 以及如果完成则返回 true意思是不会长时间运行以及可能成功完成或相反如果只有在成功完成时企图等待它或访问非抛出来的异常的结果IsCompletedSuccessfully 属性返回 true 。对于开发者所想的所有热路径举个例子开发者想要避免一些额外的开销而这些开销只在一些必要的径上才会有可以在执行这些操作之前检查这些属性这些操作实际上使用 ValueTask / ValueTaskTResult如 .await,.AsTask()。例如在 .NET Core 2.1 中 SocketsHttpHandler 的实现代码对连接读操作它返回 ValueTaskint。如果操作同步完成那么我们无需担心这个操作是否能被取消。但是如果是异步完成的那么在正在运行时我们想要取消操作那么这个取消请求将会关闭连接。这个代码是非常常用的并且分析显示它只会有一点点不同这个代码本质上结构如下int bytesRead;
{ValueTaskint readTask _connection.ReadAsync(buffer);if(readTask.IsCompletedSuccessfully){bytesRead readTask.Result;}else{using(_connection.RegisterCancellation()){bytesRead await readTask;}}
}这种模式是可接受的因为 ValueTaskint 是不会在调用 .Result 或 await 之后再次使用的。是否每个新的异步 API 都应返回 ValueTask / ValueTask ?不默认的选择任然还是 Task / TaskTResult。正如上面强调的Task / TaskTResult 要比 ValueTask / ValueTaskTResult 更容易正确使用除非性能影响要大于可用性影响Task / TaskTResult 任然是优先考虑的。返回 ValueTaskTResult 取代 TaskTResult 会有一些较小的开销例如在微基准测试中等待 TaskTResult 要比等待 ValueTaskTResult 快所以如果你要使用缓存任务如你返回 Task / Taskbool 的 API在性能方面坚持使用 Task / TaskTResult 可能会更好。ValueTask / ValueTaskTResult 也是多字相同大小的在他们等待的时候它们的字段存储在一个正在调用异步方法的状态机中它们会在相同的状态机中消耗更多的空间。然而ValueTask / ValueTaskTResult 有时也是更好的选择a你期望在你的 API 中只用直接 await 他们b在你的 API 避免相关的分配开销是重要的c无论你是否期望同步完成是通用情况你都能够有效的将对象池用于异步完成。当添加 abstract,virtual,interface 方法时你也需要考虑这些场景将会在复写/实现中存在。ValueTask 和 ValueTask 的下一步是什么对于 .NET 核心库我们讲会继续看到新的 API 返回 Task / ValueTaskTResult但是我们也能看到在合适的地方返回 ValueTask / ValueTaskTResult 的 API。据其中一个关键的例子计划在 .NET Core 3.0 提供新的 IAsyncEnuerator支持。IEnumeratorT 暴露了一个返回 bool 的MoveNext 方法并且异步 IAsyncEnumeratorT 提供了 MoveNextAsync 方法。当我们初始化开始设计这个特性的时候我们想过 MoveNextAsync 返回 Taskbool这样能够非常高效对比通用的 MoveNextAsync 同步完成的情况。但是考虑到我们期望的异步枚举影响是很广泛的并且它们都是基于接口会有很多不同的实现其中一些可能非常关注性能和内存分配考虑到绝大多数的消费者都将通过 await fearch 语言支持我们将 MoveNextAsync 改成返回类型为 ValueTaskbool。这样就允许在同步完成场景下更快也能优化实现可重用对象能够使异步完成更加减少分配。实际上当实现异步迭代器时C# 编译器就会利用这点能让异步迭代器尽可能降低分配。原文地址https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/.NET社区新闻深度好文欢迎访问公众号文章汇总 http://www.csharpkit.com