Why use Task <T> over ValueTask <T> in C #?
As in C # 7.0, async methods can return a ValueTask <T>. The explanation says that it should be used when we have a cached result or async simulation through synchronous code. However, I still do not understand what the problem of using ValueTask always or really is, why async / await was not created with the type value from the very beginning. When will ValueTask not do the job?
From the API docs (highlighted by me):
Methods can return an instance of this type of value when the probability that the result of their operations will be available synchronously and when it is expected that the method will be called so often that the cost of allocating a new
Task<TResult>for each call will be prohibitive.There are tradeoffs using
ValueTask<TResult>instead ofTask<TResult>. For example, while aValueTask<TResult>can help to avoid highlighting when a successful result is available synchronously, it also contains two fields, whileTask<TResult>as a reference type is one field. This means that the method call ends with the return of two values ββof the data value instead of one, which is more data to copy. This also means that if the method that returns one of them is expected in theasyncmethod, the state machine for thisasyncmethod will be larger because of the need to store the structure so that two fields instead of one link.In addition, for use other than consuming the result of an asynchronous operation via
await,ValueTask<TResult>can lead to a more confusing programming model, which, in turn, can lead to more distributions. For example, consider a method that can return eitherTask<TResult>with a cached task as a general result, orValueTask<TResult>. If the consumer of the result wants to use it as aTask<TResult>, for example, for use with methods such asTask.WhenAllandTask.WhenAny, you first need to convert theValueTask<TResult>toTask<TResult>usingAsTask, which leads to distribution, which could have been avoided if the cachedTask<TResult>.Thus, the default choice for any asynchronous method should be to return a
TaskorTask<TResult>. Only if performance analysis proves worth usingValueTask<TResult>instead ofTask<TResult>.
However, I still do not understand what the problem of using ValueTask is always
Types of designs are not free. Copying structures larger than the size of the link can be slower than copying the link. Saving structures that are larger than a link takes up more memory than saving a link. Structures that are larger than 64 bits may not be registered if the link can be registered. The benefits of reducing manifold pressure cannot exceed costs.
Engineering tasks must be applied to performance objectives. Set goals, measure your progress against goals, and then decide how to change the program if goals are not being met, measuring along the way to make sure your changes are truly improved.
why async / await was not created with a value type from the very beginning.
await was added in C # long after the Task<T> type already existed. It would be somewhat vicious to come up with a new type when it already existed. And await went through many design iterations before plunging into the one that was sent in 2012. The ideal is the enemy of good; itβs better to send a solution that works well with the existing infrastructure, and then, if there is consumer demand, it will be improved later.
I also note that a new feature that allows user-entered types to be the result of a compiler-generated method increases the significant risk and testing burden. When the only things you can return are invalid or a task, the testing team should not consider any scenario in which some absolutely crazy type is returned. Testing the compiler means finding out not only what programs people can write, but also what programs can be written, because we want the compiler to compile all legal programs, and not just all reasonable programs. It is expensive.
Can someone explain when ValueTask will not do the job?
The purpose of this thing is to increase productivity. He does not do this work if it is not noticeable and significantly improves performance. There is no guarantee that it will be.
ValueTask<T> not a subset of Task<T> , it is a superset .
ValueTask<T>is a discriminated union of T and aTask<T>, which makes it without highlighting forReadAsync<T>to synchronously return the value of T that it has (as opposed to usingTask.FromResult<T>, which needs to be highlighted aTask<T>).ValueTask<T>is expected, so the highest instance consumption will be indistinguishable fromTask<T>.ValueTask, being a structure, allows you to write async methods that do not allocate memory when they are run synchronously, without compromising API consistency. Imagine you have an interface with a task return method. Each class implementing this interface should return a Task, even if they are executed synchronously (I hope using Task.FromResult). Of course, two different methods can be used on the interface: synchronous and asynchronous, but this requires two different implementations to avoid "synchronization by asynchronous" and "asynchronous synchronization".
Thus, it allows you to write one method that is asynchronous or synchronous, rather than writing the same method for each of them. You can use it wherever you use Task<T> , but this often does not add anything.
Well, this adds one: it adds an implied promise to the caller that the method actually uses the extra functions that ValueTask<T> provides. I personally prefer to choose the options and return types that tell the caller as much as possible. Do not return an IList<T> if the listing cannot provide an invoice; do not return an IEnumerable<T> if possible. Your consumers do not need to look for any documentation to know which of your methods can reasonably be called synchronously and which are not.
I do not see future design changes as a convincing argument. On the contrary: if a method changes its semantics, it must break the assembly until all calls to it are updated accordingly. If this is considered undesirable (and believe me, I sympathize with the desire not to break the assembly), consider interface versioning.
This, in fact, is what is for strong typing.
If some of the programmers developing asynchronous methods in your store cannot make informed decisions, it may be helpful to appoint a senior mentor for each of the less experienced programmers and conduct a weekly code review. If they are mistaken, explain why this should be done differently. This is overhead for the older guys, but it will make juniors accelerate much faster than just throwing them into a deep corner and giving them some kind of arbitrary rule.
If the guy who wrote the method does not know whether it can be called synchronously, who is doing on Earth ?!
If you have many inexperienced programmers who write asynchronous methods, do the same people call them? Are they qualified to figure out for themselves which ones are safe to call asynchronous, or will they begin to apply a similar arbitrary rule to how they call these things?
The problem here is not your return types, but the fact that programmers are put in roles that they are not ready for. This was supposed to happen for some reason, so I am sure that this cannot be trivially fixed. Describing this, of course, is not a solution. But looking for a way to get into the problem past the compiler is also not a solution.
There are some changes in .Net Core 2.1. Starting with the .net 2.1 kernel, ValueTask can represent not only synchronously executed actions, but also asynchronous execution. In addition, we get the non-universal ValueTask type.
I will leave a comment to Stephen Taub related to your question:
We still need to formalize the manual, but I expect it to be something like this for the public surface of the API:
The challenge provides the greatest usability.
ValueTask provides more options for optimizing performance.
- If you are writing an interface / virtual method that others will override, ValueTask would be the right choice by default.
- If you expect the API to be used on hot roads where distribution will make a difference, ValueTask would be a good choice.
- Otherwise, if performance is not critical, Task is used by default, as it provides better performance. Warranty and usability.
In terms of implementation, many of the returned ValueTask instances will continue to be supported by Task.
The function can be used not only in .net core 2.1. You can use it with the System.Threading.Tasks.Extensions package.