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?

+135
c # asynchronous
Mar 24 '17 at 13:16
source share
4 answers

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 of Task<TResult> . For example, while a ValueTask<TResult> can help to avoid highlighting when a successful result is available synchronously, it also contains two fields, while Task<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 the async method, the state machine for this async method 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 either Task<TResult> with a cached task as a general result, or ValueTask<TResult> . If the consumer of the result wants to use it as a Task<TResult> , for example, for use with methods such as Task.WhenAll and Task.WhenAny , you first need to convert the ValueTask<TResult> to Task<TResult> using AsTask , which leads to distribution, which could have been avoided if the cached Task<TResult> .

Thus, the default choice for any asynchronous method should be to return a Task or Task<TResult> . Only if performance analysis proves worth using ValueTask<TResult> instead of Task<TResult> .

+207
Mar 24 '17 at 15:44
source share

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.

+89
Mar 24 '17 at 16:25
source share

ValueTask<T> not a subset of Task<T> , it is a superset .

ValueTask<T> is a discriminated union of T and a Task<T> , which makes it without highlighting for ReadAsync<T> to synchronously return the value of T that it has (as opposed to using Task.FromResult<T> , which needs to be highlighted a Task<T> ). ValueTask<T> is expected, so the highest instance consumption will be indistinguishable from Task<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.

+23
Mar 24 '17 at 13:23
source share

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.

+13
Mar 06 '18 at 14:21
source share



All Articles