Search code examples
c#async-awaitvaluetask

How can I call a Task & ValueTask inside a ValueTask?


I want to call the following code:

public async ValueTask UserGivenClaim(AppUser user, Organization org, string claim)
{
    var notify = new NotifyClaims(user, org);
    await notify.SetReplyToTask(null, org);
    await NotificationQueue.PushValueTask(notify);
}

In the code above SetReplyToTask() returns a Task and PushValueTask() returns a ValueTask. And from a performance point of view SetReplyToTask() 99% of the time does an if check that's true and returns. So 99% of the time it is not calling Task methods inside of it.

Declaring UserGivenClaim async lets me call await notify.SetReplyToTask(null, org); Will this be the fastest code? I worry that I've thrown away the advantage of ValueTask doing this.

The other possibility is to make notify.SetReplyToTask() a ValueTask. If 99% of the time it is not calling a Task, is this when I should be using ValueTask?


Solution

  • The awaits in a ValueTask returning async method don't all have to be the same awaitable type, i.e. only ValueTask or only Task. Your example as is fine.

    One of the main points of using a ValueTask is when the async method will complete synchronously as you acknowledged:

    If 99% of the time it is not calling a Task, is this when I should be using ValueTask?

    So if

    1. having PushValueTask() as ValueTask returning we are sure that it will complete synchronously the majority of the time
    2. and having the knowledge that SetReplyToTask() although Task returning, will also complete synchronously 99% of the time per your words.

    will make UserGivenClaim itself complete synchronously the majority of the time (unless there are some interesting hidden dependencies between the two method calls inside of it).

    That said, the documentation lists two other "requirements" that should be fulfilled before you abandon the default Task:

    A method may return an instance of this value type when it's likely that the result of its operation will be available synchronously, and when it's expected to be invoked so frequently that the cost of allocating a new Task for each call will be prohibitive.

    You haven't mentioned anything about those two (frequent invocation, allocation costs) for your use case, so I think you might be unnecessarily overoptimizing by thinking about using a ValueTask instead of a Task.

    In the docs you can see a lot of limitations that ValueTask has that Task doesn't, which make the choice between the two a bit more nuanced than "Is it completing synchronously the majority of the time".