Search code examples
c#.net-coreasync-awaittask-parallel-libraryvaluetask

Should I return ValueTask if I still use TaskCompletionSource to implement asynchrony?


Is there still a benefit in returning ValueTask if I use TaskCompletionSource to implement actual asynchrony?

As I understand it, the purpose of ValueTask is to reduce allocations, but allocations are still there when awaiting TaskCompletionSource.Task. Here is a simple example to illustrate the question:

async ValueTask DispatcherTimerDelayAsync(TimeSpan delay) 
{
    var tcs = new TaskCompletionSource<bool>();
    var timer = new DispatcherTimer();
    timer.Interval = delay;
    timer.Tick += (s, e) => tcs.SetResult(true);
    timer.Start();
    try
    {
        await tcs.Task;
    }
    finally
    {
        timer.Stop();
    }
}

Should I rather be returning Task (versus ValueTask) from DispatcherTimerDelayAsync, which itself is always expected to be asynchronous?


Solution

  • There are pros and cons of each. In the "pro" column:

    1. When returning a result synchronously (i.e. Task<T>), using ValueTask<T> avoids an allocation of the task - however, this doesn't apply for "void" (i.e. non-generic Task), since you can just return Task.CompletedTask
    2. When you are issuing multiple sequential awaitables, you can use a single "value task source" with sequential tokens to reduce allocations

    (a special-case of "2" might be amortization via tools like PooledAwait)

    It doesn't feel like either of those apply here, but: if in doubt, remember that it is allocation-free to return a Task as a ValueTask (return new ValueTask(task);), which would allow you to consider changing the implementation to an allocation-free one later without breaking the signature. You still pay for the original Task etc of course - but exposing them as ValueTask doesn't add any extra.

    In all honesty, I'm not sure I'd worry too much about this in this case, since a delay is ways going to be allocatey.