3

If an API returns a ValueTask or ValueTask<T>, is there a way to perform a ContinueWith on it, like I'm able to do with Task? Is there a Microsoft-provided NuGet library for doing so with .NET Standard 2.0?

D.R.
  • 20,268
  • 21
  • 102
  • 205
  • 1
    `ContinueWith` is the legacy API. Frankly, you shouldn't be using it **at all**. You should `await` both tasks and value-tasks. – Marc Gravell Mar 10 '21 at 20:35

3 Answers3

11

Use valueTask.AsTask(). AsTask() is an escape hatch for just such use cases as yours. The purpose of ValueTask is to avoid allocations whenever possible, but if you're going to invoke a continuation on another thread, you will need to allocate something and it might as well be a Task<T>.

Anton Tykhyy
  • 19,370
  • 5
  • 54
  • 56
6

Here you are:

public static async ValueTask ContinueWith<TResult>(this ValueTask<TResult> source,
    Action<ValueTask<TResult>> continuationAction)
{
    // The source task is consumed after the await, and cannot be used further.
    ValueTask<TResult> completed;
    try
    {
        completed = new(await source.ConfigureAwait(false));
    }
    catch (OperationCanceledException oce)
    {
        var tcs = new TaskCompletionSource<TResult>();
        tcs.SetCanceled(oce.CancellationToken);
        completed = new(tcs.Task);
    }
    catch (Exception ex)
    {
        completed = new(Task.FromException<TResult>(ex));
    }
    continuationAction(completed);
}

A simpler implementation would be to Preserve the ValueTask<TResult> before the await, and then invoke the continuationAction passing the preserved task as argument. This would cause invariably an allocation though, even in the case of the successful completion of the task.

The reason that I am avoiding the one-line Task.FromCanceled method in the cancellation case, is because this method requires that the supplied CancellationToken is canceled, otherwise it throws an ArgumentOutOfRangeException.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
3

I know I am posting it too late. However wanted to share the approach I used...

ValueTask<T> vt = ValueTaskTReturningMethod();
var awt = vt.GetAwaiter();
awt.OnCompleted(() => {
   T val = awt.GetResult();
   //code you want to execute after async operation completes
}
Ajit S
  • 31
  • 2