In the previous articles, we explored various aspects of asynchronous programming in .NET. We talked about the motivation behind this way of programming and about some general pitfalls and guidelines. Apart from that, we explored Task-Based Asynchronous Pattern. In that article, we saw how we can use Task and Task class for making our code asynchronous and reap the benefits of parallel execution of the code. However, using Task or Task class has one performance bottleneck that we haven’t mentioned in the previous articles.
In a nutshell, these classes cause the unnecessary allocation when the result is immediately available. This means, that even if a result is already available, a new object of Task or Task will always be created. Now, we mentioned that the async/await concept that we used in the previous articles has been around since 4.5 version of .NET. This feature has been enhanced since C# 7, i.e. .NET 4.7 version with ValueTask structure, that can be the return of the async functions.
ValueTask structure first appeared on corefxlab repository back in 2015. This repository is used for experimentation and exploring new ideas that may or may not make it into the main corefx repository. Corefx repository is the repository where all .NET Core fundamental libraries are. It was developed and suggested by the Stephen Taub for System.Threading.Tasks.Channels library. Back then Stephen provided a short explanation of it:
ValueTaskis a discriminated union of a
Task, making it allocation-free for
ReadAsyncto synchronously return a
Tvalue it has available (in contrast to using
Task.FromResult, which needs to allocate a
ValueTaskis awaitable, so most consumption of instances will be indistinguishable from with a
A lot of people saw benefits of using this structure and it was included in C#7 as a part of System.Threading.Tasks.Extensions NuGet package. So, before we dive into the ValueTask structure, let’s examine the problem it is used to solve. Since Task (Task) is a reference type, returning Task object from async method means allocating it on heap every time. And this is needed in many cases.
However, there are cases where the async method returns a cached result or completes synchronously. In these cases, this allocation is unnecessary and can become costly in performance critical parts of the code. Up to .NET 4.7 version there was no way to avoid this because an async method had to return Task, Task<T>, or void (the last one is generally frowned upon). In this version of .NET this was extended, meaning async methods could return any type as long as it has GetAwaiter method accessible. ValueTask is a concrete example of such type, that was added to this version too.
You can explore corefx repository and take a look at the complete implementation of ValueTask, but here is the part of the API that we are interested in:
Being a structure, ValueTask enables writing async methods that do not allocate memory when running synchronously. API consistency of async/await concept has not been compromised this way. Apart from that, this structure can be awaited by itself which makes it easy to use. For example, if we run this simple code:
In MultiplyAsync method, we are simulating the case where we want to avoid the use of the Task and just return a simple integer. That is done in the if statement of the method, where we are basically checking if either of the passed arguments is zero. The problem is that above code will create a Task object even if we condition in the if statement is true. We solve this problem like this:
Benefits and Tradeoffs
As mentioned previously, there are two main benefits of using ValueTask:
- Performance improvements
- Increased implementation flexibility
So, what are the numbers behind the performance improvements? Observe this code:
If we run this code, it will take 120ns with JIT to be executed. Now, if we replace Task with ValueTask like this:
we would get the execution time of 65ns with JIT. Now, it is true that because of the Task.Delay we don’t have the synchronous execution, still, we see improvements in the execution time.
Another benefit that we mentioned is increased implementation flexibility. Now, what does this exactly mean? Well, implementations of an async interface that should be synchronous would be forced to use either Task.Run or Task.FromResult. This would, of course, result in performance issues that we discussed previously. When we use ValueTask we have more possibility to choose between synchronous or asynchronous implementation. Keep in mind that this may indicate that if you have this case your code might not be designed well.
For example, observe this interface:
And let’s say that you want to call it from the code like this:
Because we used ValueTask in the interface, implementation of that interface can be either synchronous or asynchronous. We can reap this benefit and basically skip adding some functions into IThing that would handle synchronous behaviors. Using this interface is much easier this way. Here is a synchronous implementation of the interface above:
And here is an asynchronous implementation of the same interface:
However, there are some tradeoffs that we must take into consideration before using ValueTask. It is easy to think that using of ValueTask instead of Task should be done by default, which is of course not the case. For example, even though ValueTask helps us avoid unnecessary allocation in the cases when a result is available synchronously, it also contains two fields.
It is important to remember that this is a structure we use here, meaning we use value type and all its burdens. The Task on the other hand, is a reference type with just one field. When you use ValueTask we have more data to handle and take care of. If such method is awaited within an async method, the state machine for that async method will be larger as well, because storing the whole structure, in general, requires more space than storing single reference.
This is why guys from Microsoft are actually suggesting to use Task or Task as default return type form async methods. Only after performance analysis, one should consider using ValueTask instead.
ValueTask is a structure that was introduced in .NET 4.7 and that gives us quite a few possibilities when working with asynchronous methods within .NET. However, it doesn’t come without a price. It is quite good for performance critical methods that are executed synchronously. Using them we avoid allocation of the unnecessary objects. Still, as a value type, it comes with all the problems that value types in general have. So, we can reap benefits of this structure, but we have to be cautious.
Read more posts from the author at Rubik’s Code.