Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The ValueTask<TResult> and the async state machine

According to the documentation a ValueTask<TResult>...

Provides a value type that wraps a Task<TResult> and a TResult, only one of which is used.

My question is about the state machine that the C# compiler generates when the async keyword is encountered. Is it smart enough to generate a ValueTask<TResult> that wraps a TResult, when the result is available immediately, or one that wraps a Task<TResult>, when the result comes after an await? Here is an example:

static async ValueTask<DateTime> GetNowAsync(bool withDelay)
{
    if (withDelay) await Task.Delay(1000);
    return DateTime.Now;
}

static void Test()
{
    var t1 = GetNowAsync(false);
    var t2 = GetNowAsync(true);
}

Calling GetNowAsync(false) should return a TResult wrapper, because nothing is awaited, and calling GetNowAsync(true) should return a Task<TResult> wrapper, because a Task.Delay is awaited before the result becomes available. I am worried about the possibility that the state machine always returns Task wrappers, nullifying all the advantages of the ValueTask type over the Task (and keeping all the disadvantages). As far as I can tell the properties of the type ValueTask<TResult> offer no indication about what it wraps internally. I pasted the code above to sharplab.io, but the output didn't help me to answer this question either.

like image 349
Theodor Zoulias Avatar asked Aug 10 '19 06:08

Theodor Zoulias


People also ask

What is a ValueTask?

An instance created with the parameterless constructor or by the default(ValueTask<TResult>) syntax (a zero-initialized structure) represents a synchronously, successfully completed operation with a result of default(TResult) .

What is the difference between task and ValueTask?

As you know Task is a reference type and it is allocated on the heap but on the contrary, ValueTask is a value type and it is initialized on the stack so it would make a better performance in this scenario.

Where can I use ValueTask?

Use Task when you have a piece of code that will always be asynchronous, i.e., when the operation will not immediately complete. Take advantage of ValueTask when the result of an asynchronous operation is already available or when you already have a cached result.

Is an async method that returns task a return keyword?

DoSomething()' is an async method that returns 'Task', a return keyword must not be followed by an object expression.

Can I await a ValueTask / ValueTask TResult concurrently?

Awaiting a ValueTask / ValueTask<TResult> concurrently. The underlying object expects to work with only a single callback from a single consumer at a time, and attempting to await it concurrently could easily introduce race conditions and subtle program errors.

Is it possible to return a ValueTask from an asynchronous method?

Beginning with C# 7.0, an asynchronous method also can return ValueTask (available as part of the System.Threading.Tasks.Extensions package) or ValueTask<T>. This article presents a discussion of how we can work with ValueTask in C#.

When to use ValueTask and valuetaskas instead of Task?

As of .NET Core 2.0, you can use ValueTask and ValueTaskas return types for your async methods. Its important to understand when and if you should use these instead of a Task and what the trade-offs are if you decide to use it.

What is the difference between astask() and task<TResult>?

In contrast, Task / Task<TResult> do enable this, blocking the caller until the task completes. If you have a ValueTask or a ValueTask<TResult> and you need to do one of these things, you should use .AsTask() to get a Task / Task<TResult> and then operate on that resulting task object.


1 Answers

I guess I should answer my own question, since I know the answer now. The answer is that my worries were unwarranted: the C# compiler is smart enough to emit the right type of ValueTask<TResult> in every case. It emits a value-wrapper when the result is synchronously available, and a task-wrapper when it isn't.

I came to this conclusion by performance measurements: by measuring the memory allocated in each case, and the time needed to create the same amount of tasks. The results are clear and consistent. For example a ValueTask<int> consumes exactly 12 bytes when it wraps an int value, and exactly 48 bytes when it wraps a Task<int>, so there is no doubt about what's going on under the hoods.

like image 199
Theodor Zoulias Avatar answered Sep 28 '22 02:09

Theodor Zoulias