如何使用 C# 中的 ValueTask
<h4 id="在-c-中利用-valuetask-避免从异步方法返回-task-对象时分配">在 C# 中利用 <code>ValueTask</code> 避免从异步方法返回 <code>Task</code> 对象时分配</h4><blockquote>
<p>翻译自 Joydip Kanjilal 2020年7月6日 的文章 《How to use ValueTask in C#》</p>
</blockquote>
<p>异步编程已经使用了相当长一段时间了。近年来,随着 <code>async</code> 和 <code>await</code> 关键字的引入,它变得更加强大。您可以利用异步编程来提高应用程序的响应能力和吞吐量。</p>
<p>C# 中异步方法的推荐返回类型是 <code>Task</code>。如果您想编写一个有返回值的异步方法,那么应该返回 <code>Task<T></code>; 如果想编写事件处理程序,则可以返回 <code>void</code>。在 C# 7.0 之前,异步方法可以返回 <code>Task</code>、<code>Task<T></code> 或 <code>void</code>。从 C# 7.0 开始,异步方法还可以返回 <code>ValueTask</code>(作为 <code>System.Threading.Tasks.Extensions</code> 包的一部分可用)或 <code>ValueTask<T></code>。本文就讨论一下如何在 C# 中使用 <code>ValueTask</code>。</p>
<p>要使用本文提供的代码示例,您的系统中需要安装 Visual Studio 2019。如果还没有安装,您可以在这里下载 Visual Studio 2019。</p>
<h2 id="在-visual-studio-中创建一个-net-core-控制台应用程序项目">在 Visual Studio 中创建一个 .NET Core 控制台应用程序项目</h2>
<p>首先,让我们在 Visual Studio 中创建一个 .NET Core 控制台应用程序项目。假设您的系统中安装了 Visual Studio 2019,请按照下面描述的步骤在 Visual Studio 中创建一个新的 .NET Core 控制台应用程序项目。</p>
<ol>
<li>启动 Visual Studio IDE。</li>
<li>点击 “创建新项目”。</li>
<li>在 “创建新项目” 窗口中,从显示的模板列表中选择 “控制台应用(.NET Core)”。</li>
<li>点击 “下一步”。</li>
<li>在接下来显示的 “配置新项目” 窗口,指定新项目的名称和位置。</li>
<li>点击 “创建”。</li>
</ol>
<p>这将在 Visual Studio 2019 中创建一个新的 .NET Core 控制台应用程序项目。我们将在本文后面的部分中使用这个项目来说明 <code>ValueTask</code> 的用法。</p>
<h2 id="为什么要使用-valuetask-">为什么要使用 ValueTask ?</h2>
<p><code>Task</code> 表示某个操作的状态,即此操作是否完成、取消等。异步方法可以返回 <code>Task</code> 或者 <code>ValueTask</code>。</p>
<p>现在,由于 <code>Task</code> 是一个引用类型,从异步方法返回一个 <code>Task</code> 对象意味着每次调用该方法时都会在托管堆(<code>managed heap</code>)上分配该对象。因此,在使用 <code>Task</code> 时需要注意的一点是,每次从方法返回 <code>Task</code> 对象时都需要在托管堆中分配内存。如果你的方法执行的操作的结果立即可用或同步完成,则不需要这种分配,因此代价很高。</p>
<p>这正是 <code>ValueTask</code> 要出手相助的目的,<code>ValueTask<T></code> 提供了两个主要好处。首先,<code>ValueTask<T></code> 提高了性能,因为它不需要在堆(<code>heap</code>)中分配; 其次,它的实现既简单又灵活。当结果立即可用时,通过从异步方法返回 <code>ValueTask<T></code> 代替 <code>Task<T></code>,你可以避免不必要的分配开销,因为这里的 “T” 表示一个结构,而 C# 中的结构体(<code>struct</code>)是一个值类型(与 <code>Task<T></code> 中表示类的 “T” 不同)。</p>
<p>C# 中 <code>Task</code> 和 <code>ValueTask</code> 表示两种主要的 “可等待(awaitable)” 类型。请注意,您不能阻塞(block)一个 <code>ValueTask</code>。如果需要阻塞,则应使用 <code>AsTask</code> 方法将 <code>ValueTask</code> 转换为 <code>Task</code>,然后在该引用 <code>Task</code> 对象上进行阻塞。</p>
<p>另外请注意,每个 <code>ValueTask</code> 只能被消费(consumed)一次。这里的单词 “消费(consume)” 是指 <code>ValueTask</code> 可以异步等待(<code>await</code>)操作完成,或者利用 <code>AsTask</code> 将 <code>ValueTask</code> 转换为 <code>Task</code>。但是,<code>ValueTask</code> 只应被消费(consumed)一次,之后 <code>ValueTask<T></code> 应被忽略。</p>
<h2 id="c-中的-valuetask-示例">C# 中的 ValueTask 示例</h2>
<p>假设有一个异步方法返回一个 <code>Task</code>。你可以利用 <code>Task.FromResult</code> 创建 <code>Task</code> 对象,如下面给出的代码片段所示。</p>
<pre><code class="language-csharp">public Task<int> GetCustomerIdAsync()
{
return Task.FromResult(1);
}
</code></pre>
<p>上面的代码片段并没有创建整个异步状态机制,但它在托管堆(<code>managed heap</code>)中分配了一个 <code>Task</code> 对象。为了避免这种分配,您可能希望利用 <code>ValueTask</code> 代替,像下面给出的代码片段所示的那样。</p>
<pre><code class="language-csharp">public ValueTask<int> GetCustomerIdAsync()
{
return new ValueTask<int>(1);
}
</code></pre>
<p>下面的代码片段演示了 <code>ValueTask</code> 的同步实现。</p>
<pre><code class="language-csharp">public interface IRepository<T>
{
ValueTask<T> GetData();
}
</code></pre>
<p><code>Repository</code> 类扩展了 <code>IRepository</code> 接口,并实现了如下所示的方法。</p>
<pre><code class="language-csharp">public class Repository<T> : IRepository<T>
{
public ValueTask<T> GetData()
{
var value = default(T);
return new ValueTask<T>(value);
}
}
</code></pre>
<p>下面是如何从 <code>Main</code> 方法调用 <code>GetData</code> 方法。</p>
<pre><code class="language-csharp">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();
}
</code></pre>
<p>现在让我们将另一个方法添加到我们的存储库(repository)中,这次是一个名为 <code>GetDataAsync</code> 的异步方法。以下是修改后的 <code>IRepository</code> 接口的样子。</p>
<pre><code class="language-csharp">public interface IRepository<T>
{
ValueTask<T> GetData();
ValueTask<T> GetDataAsync();
}
</code></pre>
<p><code>GetDataAsync</code> 方法由 <code>Repository</code> 类实现,如下面给出的代码片段所示。</p>
<pre><code class="language-csharp">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;
}
}
</code></pre>
<h2 id="c-中应该在什么时候使用-valuetask-">C# 中应该在什么时候使用 ValueTask ?</h2>
<p>尽管 <code>ValueTask</code> 提供了一些好处,但是使用 <code>ValueTask</code> 代替 <code>Task</code> 有一定的权衡。<code>ValueTask</code> 是具有两个字段的值类型,而 <code>Task</code> 是具有单个字段的引用类型。因此,使用 <code>ValueTask</code> 意味着要处理更多的数据,因为方法调用将返回两个数据字段而不是一个。另外,如果您等待(<code>await</code>)一个返回 <code>ValueTask</code> 的方法,那么该异步方法的状态机也会更大,因为它必须容纳一个包含两个字段的结构体而不是在使用 <code>Task</code> 时的单个引用。</p>
<p>此外,如果异步方法的使用者使用 <code>Task.WhenAll</code> 或者 <code>Task.WhenAny</code>,在异步方法中使用 <code>ValueTask<T></code> 作为返回类型可能会代价很高。这是因为您需要使用 <code>AsTask</code> 方法将 <code>ValueTask<T></code> 转换为 <code>Task<T></code>,这会引发一个分配,而如果使用起初缓存的 <code>Task<T></code>,则可以轻松避免这种分配。</p>
<p><strong>经验法则是这样的:当您有一段代码总是异步的时,即当操作(总是)不能立即完成时,请使用 <code>Task</code>。当异步操作的结果已经可用时,或者当您已经缓存了结果时,请利用 <code>ValueTask</code>。不管怎样,在考虑使用 <code>ValueTask</code> 之前,您都应该执行必要的性能分析。</strong></p>
<br>
<blockquote>
<p><code>ValueTask</code> 是 <code>readonly struct</code> 类型,<code>Task</code> 是 <code>class</code> 类型。<br>
相关链接:C# 中 Struct 和 Class 的区别总结。</p>
</blockquote>
<br>
<blockquote>
<p>作者 : Joydip Kanjilal<br>
译者 : 技术译民<br>
出品 : 技术译站<br>
链接 : 英文原文</p>
</blockquote>
</div>
<div id="MySignature" role="contentinfo">
<div><p style="font-size: 14px; font-family: '微软雅黑';font-weight: 400; padding: 0 0 5px 2px;color:#888;">© 转载请标明出处 https://www.cnblogs.com/ittranslator</p></div>
<div style="text-align: center;max-width: 280px;margin: 10px auto;">
<p style="font-size: 18px; font-weight: 600; color: rgba(0, 0, 0, 1); padding-top: 6px; padding-bottom: 6px; border-bottom: 1px dashed rgba(119, 119, 255, 1)">不做标题党,只分享技术干货
</p><p style="font-size: 13px; font-weight: 400; padding-top: 6px; padding-bottom: 0px;color:rgb(66,66,166);">公众号『技术译站』,<b>欢迎扫码关注</b></p>
<img style="width: 215px;" src="https://img2020.cnblogs.com/blog/2074831/202006/2074831-20200628152541133-1651846078.jpg" alt="">
</div><br><br>
来源:https://www.cnblogs.com/ittranslator/p/13703279.html
頁:
[1]