简体   繁体   中英

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.

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.

The compiler is dumb enough to do what it's told:

https://source.dot.net/#System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs,409

[AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder<>))]
[StructLayout(LayoutKind.Auto)]
public readonly struct ValueTask<TResult> : IEquatable<ValueTask<TResult>>

Beware of using ValueTask :

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM