283

As of C# 7.0 async methods can return ValueTask<T>. The explanation says that it should be used when we have a cached result or simulating async via synchronous code. However I still do not understand what is the problem with using ValueTask always or in fact why async/await wasn't built with a value type from the start. When would ValueTask fail to do the job?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Stilgar
  • 22,354
  • 14
  • 64
  • 101
  • 13
    I suspect it's to do with the benefits of `ValueTask` (in terms of allocations) not materializing for operations that are *actually* asynchronous (because in that case `ValueTask` will still need heap allocation). There's also the matter of `Task` having a lot of other support within libraries. – Jon Skeet Mar 24 '17 at 13:21
  • I thought that by refs are cheaper. See this question about c++ http://stackoverflow.com/q/42721683/5976576. I might be missing the boat. – MotKohn Mar 24 '17 at 13:22
  • 4
    @JonSkeet existing libraries are a problem but this begs the question should Task have been ValueTask from the start? The benefits may not exist when using it for actual async stuff but is it harmful? – Stilgar Mar 24 '17 at 13:38
  • @Stilgar: That probably requires more understanding than I currently have :) (Given that it's a discriminated union, it's slightly more expensive to pass around...) – Jon Skeet Mar 24 '17 at 14:14
  • 9
    See https://github.com/dotnet/corefx/issues/4708#issuecomment-160658188 for more wisdom than I would be able to convey :) – Jon Skeet Mar 24 '17 at 14:15
  • `ValueTask` could help with chatty async APIs, see https://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Async-libraries-APIs-should-be-chunky. I guess one has to measure the perf before opt for it. – Mihail Slavchev Sep 29 '17 at 11:57
  • 1
    I can also totally recommend to have a look at this short YouTube video [Understanding how to use Task and ValueTask](https://www.youtube.com/watch?v=fj-LVS8hqIE) on the Microsoft Developers channel. – ˈvɔlə Jul 02 '19 at 12:33
  • 2
    [Prefer ValueTask to Task, always](https://blog.marcgravell.com/2019/08/prefer-valuetask-to-task-always-and.html) – Joel Mueller Aug 27 '19 at 17:40
  • 3
    @JoelMueller the plot thickens :) – Stilgar Sep 05 '19 at 19:21
  • 9
    You know it's an important question when Jon Skeet, the two Stephens (Cleary and Toub) and Eric Lippert all have valuable contributions... – solublefish Aug 31 '20 at 08:56

4 Answers4

348

From the API docs (emphasis added):

Methods may return an instance of this value type when it's likely that the result of their operations will be available synchronously and when the method is expected to be invoked so frequently that the cost of allocating a new Task<TResult> for each call will be prohibitive.

There are tradeoffs to using a ValueTask<TResult> instead of a Task<TResult>. For example, while a ValueTask<TResult> can help avoid an allocation in the case where the successful result is available synchronously, it also contains two fields whereas a Task<TResult> as a reference type is a single field. This means that a method call ends up returning two fields worth of data instead of one, which is more data to copy. It also means that if a method that returns one of these is awaited within an async method, the state machine for that async method will be larger due to needing to store the struct that's two fields instead of a single reference.

Further, for uses other than consuming the result of an asynchronous operation via await, ValueTask<TResult> can lead to a more convoluted programming model, which can in turn actually lead to more allocations. For example, consider a method that could return either a Task<TResult> with a cached task as a common result or a ValueTask<TResult>. If the consumer of the result wants to use it as a Task<TResult>, such as to use with in methods like Task.WhenAll and Task.WhenAny, the ValueTask<TResult> would first need to be converted into a Task<TResult> using AsTask, which leads to an allocation that would have been avoided if a cached Task<TResult> had been used in the first place.

As such, the default choice for any asynchronous method should be to return a Task or Task<TResult>. Only if performance analysis proves it worthwhile should a ValueTask<TResult> be used instead of Task<TResult>.

Pierre Arnaud
  • 10,212
  • 11
  • 77
  • 108
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Accepted since it is the most direct answer to the question and contains clear guidelines on what to do. – Stilgar Mar 24 '17 at 20:45
  • 13
    @MattThomas: It saves a single `Task` allocation (which is small and cheap these days), but at the cost of making the *caller's* existing allocation larger and doubling the size of the return value (impacting register allocation). While it's a clear choice for a buffered read scenario, applying it by default to all interfaces is not something I'd recommend. – Stephen Cleary Mar 27 '17 at 18:40
  • 4
    Right, either `Task` or `ValueTask` can be used as a synchronous return type (with `Task.FromResult`). But there's still value (heh) in `ValueTask` if you have something you *expect* to be synchronous. `ReadByteAsync` being a classic example. I *believe* `ValueTask` was created mainly for the new "channels" (low-level byte streams), possibly also used in ASP.NET core where performance really matters. – Stephen Cleary Mar 27 '17 at 19:07
  • Also, can I do `async ValueTask DoSomethingMaybeAsync() {return await ReturnT_Async();}`, as I can do `async Task DoSomethingAsync() {return await ReturnT_Async();}`? – Dejavu Mar 28 '17 at 00:27
  • Can you elaborate on the point mentioned in @unsafePtr 's answer? _"If you're writing an interface / virtual method that others will override, ValueTask is the right default choice."_ . For example, [the design for `IAsyncEnumerable`](https://github.com/dotnet/csharplang/blob/master/proposals/async-streams.md#iasyncenumerable--iasyncenumerator) is actually considering `Task` as the return for `MoveNext`, instead of `ValueTask`. – julealgon Mar 22 '18 at 20:44
  • @julealgon: I'm Stephen Cleary, not Stephen Toub. I'm not familiar yet with the `ValueTask` changes proposed for 2.1. – Stephen Cleary Mar 23 '18 at 03:34
  • 1
    Oh I know that lol, was just wondering if you had something to add to that specific comment ;) – julealgon Mar 23 '18 at 20:03
  • @julealgon: Ah, OK. I have had a lot of people think I'm him. :) I have nothing to add at this time, just because I haven't had time to check out all the changes yet. – Stephen Cleary Mar 24 '18 at 18:05
  • 1
    can I say "use `ValueTask` for **many short** awaits and use `Task` for **few big** awaits"? can i take this as a general rule? – M.kazem Akhgary Dec 27 '18 at 19:07
  • 1
    @M.kazemAkhgary I would say `ValueTask` would be appropriate only for many *synchronous* awaits where the allocation overhead of `Task` objects is undesirable (as measured by performance analysis). – Stephen Cleary Dec 28 '18 at 06:05
  • 5
    Does [this PR](https://github.com/dotnet/coreclr/pull/26310) switch the balance over to preferring ValueTask? (ref: https://blog.marcgravell.com/2019/08/prefer-valuetask-to-task-always-and.html) – stuartd Aug 27 '19 at 10:27
  • 5
    @stuartd: For now, I would still recommend using `Task` as a default. This is only because most developers are not familiar with the restrictions around `ValueTask` (specifically, the "only consume once" rule and the "no blocking" rule). That said, if all the devs on your team are good with `ValueTask`, then I would recommend a *team-level* guideline of preferring `ValueTask`. – Stephen Cleary Aug 27 '19 at 13:53
  • 1
    updated link: https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask-1 – mshwf Jul 23 '21 at 20:55
  • There are cases where `Task` may actually be faster, especially when working with `FromResult` due to optimizations/caching in the BCL. Never make assumptions about performance, always measure. – Honza R Aug 27 '21 at 13:33
150

However I still do not understand what is the problem with using ValueTask always

Struct types are not free. Copying structs that are larger than the size of a reference can be slower than copying a reference. Storing structs that are larger than a reference takes more memory than storing a reference. Structs that are larger than 64 bits might not be enregistered when a reference could be enregistered. The benefits of lower collection pressure may not exceed the costs.

Performance problems should be approached with an engineering discipline. Make goals, measure your progress against goals, and then decide how to modify the program if goals are not met, measuring along the way to make sure that your changes are actually improvements.

why async/await wasn't built with a value type from the start.

await was added to C# long after the Task<T> type already existed. It would have been somewhat perverse to invent a new type when one already existed. And await went through a great many design iterations before settling on the one that was shipped in 2012. The perfect is the enemy of the good; better to ship a solution that works well with the existing infrastructure and then if there is user demand, provide improvements later.

I note also that the new feature of allowing user-supplied types to be the output of a compiler-generated method adds considerable risk and testing burden. When the only things you can return are void or a task, the testing team does not have to consider any scenario in which some absolutely crazy type is returned. Testing a compiler means figuring out not just what programs people are likely to write, but what programs are possible to write, because we want the compiler to compile all legal programs, not just all sensible programs. That's expensive.

Can someone explain when ValueTask would fail to do the job?

The purpose of the thing is improved performance. It doesn't do the job if it doesn't measurably and significantly improve performance. There is no guarantee that it will.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 8
    `Structs that are larger than 64 bits might not be enregistered when a reference could be enregistered`...in case anyone else is wondering, the word "enregistered" here is probably referring to "being stored in CPU registers" (which are the fastest possible memory locations available). – Eric Mutta Feb 14 '21 at 16:34
  • 3
    I unlinked this response only so I could like it again – Leonardo Jun 25 '21 at 02:59
35

There is some changes in .Net Core 2.1. Starting from .net core 2.1 ValueTask can represent not only the synchronous completed actions but the async completed too. In addition we receive non-generic ValueTask type.

I will leave Stephen Toub comment which is related to your question:

We still need to formalize guidance, but I expect it'll be something like this for public API surface area:

  • Task provides the most usability.

  • ValueTask provides the most options for performance optimization.

  • If you're writing an interface / virtual method that others will override, ValueTask is the right default choice.

  • If you expect the API to be used on hot paths where allocations will matter, ValueTask is a good choice.

  • Otherwise, where performance isn't critical, default to Task, as it provides better guarantees and usability.

From an implementation perspective, many of the returned ValueTask instances will still be backed by Task.

Feature can be used not only in the .net core 2.1. You will be able to use it with System.Threading.Tasks.Extensions package.

Community
  • 1
  • 1
unsafePtr
  • 1,591
  • 2
  • 17
  • 27
  • 3
    More from Stephen today: https://blogs.msdn.microsoft.com/dotnet/2018/11/07/understanding-the-whys-whats-and-whens-of-valuetask/ – Mike Cheel Nov 08 '18 at 13:42
9

A more recent info from Marc (Aug 2019)

Use Task when something is usually or always going to be genuinely asynchronous, i.e. not immediately complete; use ValueTask when something is usually or always going to be synchronous, i.e. the value will be known inline; also use ValueTask in a polymorphic scenario (virtual, interface) where you can't know the answer.

Source: https://blog.marcgravell.com/2019/08/prefer-valuetask-to-task-always-and.html

I followed above blog post for a recent project when I had similar questions.

UPDATE #2 as of 23 August 2019 from Marc Gravell (From his blog):

So, going back to the earlier question of when to use Task vs ValueTask, IMO the answer is now obvious:

Use ValueTask[], unless you absolutely can't because the existing API is Task[], and even then: at least consider an API break.

And also keep in mind: Only await any single awaitable expression once

If we put those two things together, libraries and the BCL are free to work miracles in the background to improve performance without the caller needing to care.

Shubhan
  • 874
  • 7
  • 13
  • 9
    The advice on that blog post has been updated: Use ValueTask[], unless you absolutely can't because the existing API is Task[], and even then: at least consider an API break – extremeandy Dec 06 '20 at 20:23
  • "Only await any single awaitable expression once" only applies to ValueTask, as I read it. So if you need to await more than once, that seems to be a reason to use Task instead. – Jared Thirsk Aug 02 '23 at 14:55