<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Async on Allen's Dev Journal</title><link>https://allenyl30.github.io/tags/async/</link><description>Recent content in Async on Allen's Dev Journal</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>Yuhao</copyright><lastBuildDate>Wed, 23 Apr 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://allenyl30.github.io/tags/async/index.xml" rel="self" type="application/rss+xml"/><item><title>Why ValueTask? A Guide to C#'s Performance-Oriented Task Alternative</title><link>https://allenyl30.github.io/p/why-valuetask-a-guide-to-csharp-s-performance-oriented-task-alternative/</link><pubDate>Wed, 23 Apr 2025 00:00:00 +0000</pubDate><guid>https://allenyl30.github.io/p/why-valuetask-a-guide-to-csharp-s-performance-oriented-task-alternative/</guid><description>&lt;img src="https://allenyl30.github.io/p/why-valuetask-a-guide-to-csharp-s-performance-oriented-task-alternative/cover.jpg" alt="Featured image of post Why ValueTask? A Guide to C#'s Performance-Oriented Task Alternative" /&gt;&lt;p&gt;Since C# 5.0 introduced the &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt; keywords, asynchronous programming has become remarkably simple. The &lt;code&gt;Task&lt;/code&gt; type has played a crucial role in this paradigm, becoming a ubiquitous part of modern .NET development. However, with the release of .NET Core 2.0, Microsoft introduced a new type: &lt;code&gt;ValueTask&lt;/code&gt;. What is this type, why do we need it, and in what situations should we use it? Let&amp;rsquo;s explore these questions today.&lt;/p&gt;
&lt;h2 id="a-quick-recap-of-the-task-type"&gt;A Quick Recap of the &lt;code&gt;Task&lt;/code&gt; Type
&lt;/h2&gt;&lt;p&gt;In asynchronous programming, we use the &lt;code&gt;Task&lt;/code&gt; type to represent an asynchronous operation. Compared to other mainstream programming languages, C#&amp;rsquo;s tasks are incredibly lightweight. For instance, C# experts have noted that a &lt;code&gt;Task&lt;/code&gt; object typically consumes only 64-136 bytes of memory, whereas a goroutine in Go starts at a minimum of 2 KB.&lt;/p&gt;
&lt;p&gt;Furthermore, &lt;code&gt;Task&lt;/code&gt; comes with numerous optimization techniques:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Task.FromResult&lt;/code&gt;&lt;/strong&gt;: To return a completed task with a specific result.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Task.CompletedTask&lt;/code&gt;&lt;/strong&gt;: To return a singleton, already-completed task.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Task.FromCanceled&lt;/code&gt;&lt;/strong&gt;: To return a task in a canceled state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Task.FromException&lt;/code&gt;&lt;/strong&gt;: To return a task in a faulted state.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a long time, from C# 5.0 (.NET Framework 4 era) until .NET Core 2.0, &lt;code&gt;Task&lt;/code&gt; served its purpose exceptionally well.&lt;/p&gt;
&lt;h2 id="the-problem-with-the-traditional-task-type"&gt;The Problem with the Traditional &lt;code&gt;Task&lt;/code&gt; Type
&lt;/h2&gt;&lt;p&gt;As .NET evolved into a cross-platform framework, its use cases expanded dramatically. Microsoft&amp;rsquo;s ambitions grew, leading to a relentless focus on performance optimization from every angle. This effort brought us innovations like &lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;Memory&amp;lt;T&amp;gt;&lt;/code&gt;, and &lt;code&gt;ref struct&lt;/code&gt;, and it also led to the creation of &lt;code&gt;ValueTask&lt;/code&gt;. So, what was the problem with the traditional &lt;code&gt;Task&lt;/code&gt; type?&lt;/p&gt;
&lt;p&gt;First, we need to understand that &lt;code&gt;Task&lt;/code&gt; exists in both generic (&lt;code&gt;Task&amp;lt;TResult&amp;gt;&lt;/code&gt;) and non-generic versions, representing async operations with and without a return value, respectively. When &lt;code&gt;ValueTask&lt;/code&gt; was first introduced, it only had a generic version. This tells us that its initial design was focused exclusively on asynchronous methods that return a value.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s consider a classic example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ConcurrentDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;GetMessageAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;GetMessageFromDatabaseAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TryAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In the &lt;code&gt;GetMessageAsync&lt;/code&gt; method above, we first try to retrieve a message from a cache. If it&amp;rsquo;s not found, we fetch it from the database. The problem lies in the cache-hit scenario. Although it seems like we are returning a value directly, the method&amp;rsquo;s signature is &lt;code&gt;async Task&amp;lt;string&amp;gt;&lt;/code&gt;. This means that even when the result is available synchronously, the C# compiler still allocates a new &lt;code&gt;Task&amp;lt;string&amp;gt;&lt;/code&gt; object on the heap to wrap that result. This leads to unnecessary memory allocation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This scenario, where an &lt;code&gt;async&lt;/code&gt; method can return a result without ever hitting an &lt;code&gt;await&lt;/code&gt; keyword, is known as &lt;strong&gt;&amp;ldquo;synchronous completion.&amp;rdquo;&lt;/strong&gt; The operation begins and ends on the same thread.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="introducing-valuetask"&gt;Introducing &lt;code&gt;ValueTask&lt;/code&gt;
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;ValueTask&lt;/code&gt; was created to solve this very problem. It was officially introduced in .NET Core 2.0 and enhanced in .NET Core 2.1 (with the addition of the &lt;code&gt;IValueTaskSource&amp;lt;T&amp;gt;&lt;/code&gt; interface, enabling properties like &lt;code&gt;IsCompleted&lt;/code&gt;). A non-generic &lt;code&gt;ValueTask&lt;/code&gt; was also added later.&lt;/p&gt;
&lt;p&gt;Instead of thinking of &lt;code&gt;ValueTask&lt;/code&gt; as just a value type, it&amp;rsquo;s more helpful to understand it as a type that can represent &lt;em&gt;either&lt;/em&gt; a &lt;code&gt;Value&lt;/code&gt; &lt;em&gt;or&lt;/em&gt; a &lt;code&gt;Task&lt;/code&gt;. This makes it perfectly suited for the caching scenario described above. We can modify the code to use &lt;code&gt;ValueTask&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;GetMessageAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// In C# 7.2+, you can just `return message;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;GetMessageFromDatabaseAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TryAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, if the data is found in the cache, the method can return a &lt;code&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; that wraps the result directly, avoiding any heap allocation for a &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; object. If the data is not in the cache (the &amp;ldquo;cold path&amp;rdquo;), the method proceeds to &lt;code&gt;await&lt;/code&gt; the database call. In this case, &lt;code&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; will wrap an underlying &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;, and its performance will be roughly equivalent to (or very slightly slower than) using &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; directly, due to the overhead of the wrapper struct.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The non-generic &lt;code&gt;ValueTask&lt;/code&gt; is used even more rarely. It&amp;rsquo;s only beneficial in scenarios where an operation can complete asynchronously without requiring any memory allocation. Stephen Toub, a key architect behind this feature, advises against using the non-generic &lt;code&gt;ValueTask&lt;/code&gt; unless profiling has proven that the tiny allocation of a standard &lt;code&gt;Task&lt;/code&gt; is a critical performance bottleneck.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At this point, you might be thinking: &lt;code&gt;ValueTask&lt;/code&gt; is a &lt;code&gt;struct&lt;/code&gt;, so it&amp;rsquo;s allocated on the stack, which is much cheaper than heap allocation. Like &lt;code&gt;ValueTuple&lt;/code&gt; versus &lt;code&gt;Tuple&lt;/code&gt; or &lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; versus an array slice, it seems like a clear performance win.&lt;/p&gt;
&lt;p&gt;But is &lt;code&gt;ValueTask&lt;/code&gt; really that perfect? Can it completely replace &lt;code&gt;Task&lt;/code&gt;? The answer is not so simple.&lt;/p&gt;
&lt;h2 id="important-valuetask-caveats"&gt;Important &lt;code&gt;ValueTask&lt;/code&gt; Caveats
&lt;/h2&gt;&lt;p&gt;Now it&amp;rsquo;s time to discuss the important rules and limitations you must respect when using &lt;code&gt;ValueTask&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="a-valuetask-cannot-be-awaited-more-than-once"&gt;A &lt;code&gt;ValueTask&lt;/code&gt; Cannot Be Awaited More Than Once
&lt;/h3&gt;&lt;p&gt;A &lt;code&gt;ValueTask&lt;/code&gt; may wrap a reusable underlying object (an &lt;code&gt;IValueTaskSource&amp;lt;T&amp;gt;&lt;/code&gt;). Once you &lt;code&gt;await&lt;/code&gt; it and the operation completes, that underlying object may be recycled and used for another operation. Awaiting it a second time could lead to reading incorrect state or other unpredictable behavior. In contrast, a &lt;code&gt;Task&lt;/code&gt; represents a final state and can be awaited any number of times.&lt;/p&gt;
&lt;h3 id="do-not-block-on-a-valuetask"&gt;Do Not Block on a &lt;code&gt;ValueTask&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;The underlying &lt;code&gt;IValueTaskSource&amp;lt;T&amp;gt;&lt;/code&gt; that a &lt;code&gt;ValueTask&lt;/code&gt; may wrap is not required to support blocking until completion. This means you must &lt;strong&gt;not&lt;/strong&gt; call methods like &lt;code&gt;.Wait()&lt;/code&gt; or access the &lt;code&gt;.Result&lt;/code&gt; property on a &lt;code&gt;ValueTask&lt;/code&gt; that has not yet completed. Doing so is likely to cause a deadlock or throw an exception.&lt;/p&gt;
&lt;p&gt;However, if you can confirm that a &lt;code&gt;ValueTask&lt;/code&gt; is already complete (by checking its &lt;code&gt;IsCompleted&lt;/code&gt; property), it is safe to access its &lt;code&gt;.Result&lt;/code&gt; property to get the value.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Microsoft has added a specific Roslyn analyzer warning for this misuse: &lt;a class="link" href="https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2012" target="_blank" rel="noopener"
&gt;CA2012: Use ValueTask correctly&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="do-not-await-a-valuetask-concurrently"&gt;Do Not &lt;code&gt;await&lt;/code&gt; a &lt;code&gt;ValueTask&lt;/code&gt; Concurrently
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;ValueTask&lt;/code&gt; was designed as a performance optimization, not a full-featured &lt;code&gt;Task&lt;/code&gt; replacement. The underlying object is intended to be awaited by a single &amp;ldquo;consumer&amp;rdquo; at a time and is not thread-safe. Attempting to await the same &lt;code&gt;ValueTask&lt;/code&gt; from multiple threads concurrently can easily introduce race conditions and subtle bugs. A &lt;code&gt;Task&lt;/code&gt;, on the other hand, is thread-safe and supports any number of concurrent awaiters.&lt;/p&gt;
&lt;h2 id="how-to-work-around-valuetasks-limitations"&gt;How to Work Around &lt;code&gt;ValueTask&lt;/code&gt;&amp;rsquo;s Limitations
&lt;/h2&gt;&lt;p&gt;In practice, you might encounter situations where you need to overcome these limitations. Here are the recommended approaches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;To get the result synchronously&lt;/strong&gt;: If you need to block to get a &lt;code&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt;&amp;rsquo;s result, you must first check properties like &lt;code&gt;IsCompleted&lt;/code&gt; or &lt;code&gt;IsCompletedSuccessfully&lt;/code&gt;. Only after confirming it&amp;rsquo;s complete should you access the &lt;code&gt;.Result&lt;/code&gt; property.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;To await multiple times or from multiple threads&lt;/strong&gt;: Use the &lt;strong&gt;&lt;code&gt;.AsTask()&lt;/code&gt;&lt;/strong&gt; method. This will convert the &lt;code&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; into a standard &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;. If the &lt;code&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; already wraps a &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;, it returns that same instance. If it wraps a result, it will return a new, completed &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;. Once you have a &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;, you can use it with all the flexibility you&amp;rsquo;re used to.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Based on its design and limitations, a widely accepted best practice has emerged:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Best Practice:&lt;/strong&gt; In the vast majority of cases, you should &lt;strong&gt;&lt;code&gt;await&lt;/code&gt; a &lt;code&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; immediately&lt;/strong&gt; to consume its result. Avoid assigning the returned &lt;code&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; to a local variable for later use. If you need to store it, pass it around, or await it multiple times, &lt;strong&gt;convert it to a &lt;code&gt;Task&lt;/code&gt; first using &lt;code&gt;.AsTask()&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;In summary, &lt;code&gt;ValueTask&lt;/code&gt; is a powerful feature for performance-critical code, offering a way to eliminate heap allocations in &amp;ldquo;hot path&amp;rdquo; scenarios where an async method often completes synchronously. However, this performance gain comes with strict usage rules, such as the single-await limitation. Using it can feel like walking a tightrope; a misstep can lead to subtle bugs.&lt;/p&gt;
&lt;p&gt;Fortunately, the primary use case is simple and safe: &lt;code&gt;await&lt;/code&gt; the &lt;code&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; immediately. As long as you remember that it is a specialized tool and not a general-purpose replacement for &lt;code&gt;Task&lt;/code&gt;, you can leverage its benefits effectively.&lt;/p&gt;
&lt;p&gt;Hopefully, this article helps you use &lt;code&gt;ValueTask&lt;/code&gt; correctly and confidently in your projects.&lt;/p&gt;
&lt;h2 id="references"&gt;References
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs,77a292425839ae85" target="_blank" rel="noopener"
&gt;ValueTask Source Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/" target="_blank" rel="noopener"
&gt;Understanding the Whys, Whats, and Whens of ValueTask | .NET Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.codeguru.com/csharp/c-sharp-valuetask/" target="_blank" rel="noopener"
&gt;Working with ValueTask in C# | CodeGuru.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>