There is a library returning a ValueTask and I have a synchronous method which is consuming the ValueTask. The issue is that there is the following warning:
CA2012: ValueTask instances should not have their result directly accessed unless the instance has already completed. Unlike Tasks, calling Result or GetAwaiter().GetResult() on a ValueTask is not guaranteed to block until the operation completes. If you can't simply await the instance, consider first checking its IsCompleted property (or asserting it's true if you know that to be the case).
How do I fix it?
public void CreateListenKey()
{
var result = CreateSpotListenKeyAsync().GetAwaiter().GetResult(); // CA2012: ValueTask instances should not have their result directly accessed unless the instance has already completed. Unlike Tasks, calling Result or GetAwaiter().GetResult() on a ValueTask is not guaranteed to block until the operation completes. If you can't simply await the instance, consider first checking its IsCompleted property (or asserting it's true if you know that to be the case).
if (result.Success)
{
using var document = JsonDocument.Parse(result.Data!);
lock (_listenKeyLocker)
{
if (document.RootElement.TryGetProperty("listenKey", out var listenKeyElement))
{
var listenKey = listenKeyElement.GetString();
ListenKey = listenKey;
}
}
}
}
// library
public async ValueTask<CallResult<string>> CreateSpotListenKeyAsync()
{
var result = await SendPublicAsync<string>(
"/api/v3/userDataStream",
Method.Post);
return result;
}
// Can't just make it async, because these listen key methods are used in an event handler.
private void OnKeepAliveTimerElapsed(object? sender, ElapsedEventArgs e)
{
RestApi.PingListenKey();
}
The recommended way to wait synchronously the completion of a
ValueTaskis to convert it to aTask, with theAsTaskmethod:In your case, and based on the implementation of the
CreateSpotListenKeyAsyncmethod, this conversion most likely incurs zero overhead. Assuming that theValueTask<CallResult<string>>is not already completed upon creation, most likely it wraps internally aTask<CallResult<string>>, so the conversion will just return the wrapped task. It seems unlikely that it wraps an implementation of theIValueTaskSource<T>interface, in which case the conversion would incur the cost of allocating a few lightweight objects.In case you anticipate that the
ValueTask<CallResult<string>>will be frequently completed upon creation, you could optimize your code like this:The intention of above code is to avoid an internal call to the
Task.FromResultmethod, which is allocating in general.It is also possible to optimize the synchronous waiting of
IValueTaskSource<T>-based value tasks, but it's not trivial, and the expected benefits are so minuscule that it's unlikely to worth the effort.In case of a non-generic
ValueTask, you could do what @nop suggested in a comment:But it's not really needed. In case the
ValueTaskhas completed successfully, theAsTaskwill never allocate, whatever is the underlying implementation of theValueTask. In case it is backed by aIValueTaskSource, theAsTaskwill return the static singletonTask.CompletedTask. So doing simplyvalueTask.AsTask().GetAwaiter().GetResult();is pretty much the same.