Using Task or Task classes has a performance bottleneck that we didn't mention in previous articles. In short, these classes lead when the results are immediately availableUnnecessary allocation。 This means that a new Task or Task object will always be created even if the result is already available. Now, we mentioned that the async/await concept we used in previous articles has been around since .NET 4.5 release. This feature has been enhanced since C# 7 with the .NET 4.7 version with the ValueTask structure that can be used as a return for asynchronous functions.
ValueTask structure
The ValueTask structure first appeared in the corefxlab repository in 2015. This repository is used to experiment and explore new ideas that may or may not make it to the main corefx repository. The Corefx repository is the repository where all the .NET Core base libraries are located. It was developed and suggested by Stephen Taub for the System.Threading.Tasks.Channels library. At that time, Stephen provided a brief explanation:
corefxlab library address:The hyperlink login is visible.
A ValueTask is a distinct union of a T and a Task, allowing ReadAsync to freely allocate to synchronously return its available T values (unlike using Task.FromResult, which requires an allocation of a Task instance). ValueTask is waitable, so the consumption of most instances is indistinguishable from the consumption of tasks. A lot of people see the benefits of using this structure, which is included in C# 7 as part of the System.Threading.Tasks.Extensions NuGet package. So, before we dive into the ValueTask structure, let's examine the problem it uses to solve. Since Task(Task) is a reference type, start withThe asynchronous method returning the Task object means that it is allocated on the heap every time。 This is necessary in many cases.
However, in some cases, asynchronous methods return results immediately or complete synchronously. In these cases, this allocation is unnecessary and can become expensive in performance-critical parts of the code. Until the .NET 4.7 release, there was no way to avoid this, as asynchronous methods had to return Task, Task, <T>or void (the last one was usually undesirable). In this version of .NET, this is extended, meaning that an asynchronous method can return any type as long as it has an accessible GetAwaiter method. ValueTask is a concrete example of this type, and it was also added to this release.
You can browse the corefx repository and see the full implementation of ValueTask, here is the API section we are interested in:
As a structure, ValueTask allows writing asynchronous methods that do not allocate memory during synchronous runtime. API consistency of the async/await concept is not compromised in this way. In addition to this, this structure waits on its own, making it easy to use. For example, if we run this simple code:
In the MultiplyAsync method, we are simulating a situation where we want to avoid using Task and return only a simple integer. This is done in the method's if statement, where we are basically checking if the passed parameter is zero. The problem isEven if our condition in the if statement is true, the code above creates a Task object。 We solve this problem like this:
ValueTask and Task
As mentioned earlier, there are two main benefits to using ValueTask:
- Performance improvements
- Increase implementation flexibility
So, what are the numbers behind the performance improvements? Observe this code:
If we run this code, it takes 120ns to execute the JIT. Now, if we replace Task with ValueTask like this:
With JIT, we will get an execution time of 65ns. Now, it is true that due to Task.Delay we are not executing synchronously, but we see an improvement in execution time.
Another benefit we mentioned is the increased flexibility in implementation. Now, what exactly does this mean? Well, implementations of asynchronous interfaces that should be synchronized will be forced to use Task.Run or Task.FromResult. Of course, this leads to the performance issues we discussed earlier. When we use ValueTask, we are more likely to choose between synchronous or asynchronous implementations. Keep in mind that this could be a sign that your code may not be well designed if this happens to you.
For example, observe this interface:
Let's say you want to call it from code like this:
Because we use ValueTask in the interface, the implementation of the interface can be synchronous or asynchronous. We can get this benefit by basically skipping adding some functions to IThing that handle synchronization behavior. It is much easier to use this interface this way. Here is a synchronous implementation of the above interface:
Here's an asynchronous implementation of the same interface:
However, we must consider some trade-offs before using ValueTask. It's easy to think that ValueTask should be used by default instead of Task, which is certainly not the case. For example, although ValueTask helps us avoid unnecessary assignments when result synchronization is available, it also contains two fields.
It's important to remember that this is the structure we're using here, which means we're using value types and all their burdens. Task, on the other hand, is a reference type with only one field.When you use ValueTask, we have more data to process and process. If such a method is waited for in an asynchronous method, then the asynchronous method isThe state machine will also be larger, because storing the entire structure usually requires more space than storing a single reference.
That's why the folks at Microsoft actually recommend using Task or Task as the default return type for asynchronous methods. Only after performance analysis should you consider switching to ValueTask.
summary
ValueTask is a structure introduced in .NET 4.7 that gives us a lot of possibilities to use asynchronous methods in .NET. However, it is not without a price. This is useful for performance-critical methods that are executed synchronously. With them we can avoid assigning unnecessary objects. Still, as a value type, it comes with all the problems that value types usually have. Therefore, we can benefit from this structure, but we must be careful.
Original address:The hyperlink login is visible.
|