Task -> ValueTask in this case

788 Views Asked by At

SubscribeToTickerAsync is going to be invoked more than once, which makes me believe ValueTask is the better choice here. Otherwise, the impact of avoiding a single allocation of a tiny object is practically zero.

What stops me from replacing Task -> ValueTask is the following statement. Does that mean that if there are multiple awaits it's a bad idea to return ValueTask?

ValueTask can only be awaited once, and not to be awaited concurrently. This means that each ValueTask can be consumed only once.

Here the word “consume” implies that a ValueTask can asynchronously wait for (await) the operation to complete or take advantage of AsTask to convert a ValueTask to a Task. However, a ValueTask should be consumed only once, after which the ValueTask should be ignored.

_requestManager.GetResponseAsync is a TaskCompletionSource<T, Y> implementation.

_websocketClient.SendAsync is technically System.Threading.Channels implementation.

public ValueTask SendAsync(Message message)
{
    return _sendChannel.Writer.WriteAsync(message);
}

Code

private async Task SendAsync(SocketRequest request)
{
    await _throttlingLimiter.WaitAsync().ConfigureAwait(false);

    var json = JsonSerializer.Serialize(request);
    var message = new Message(Encoding.UTF8.GetBytes(json));
    await _websocketClient.SendAsync(message).ConfigureAwait(false);

    var responseTask = await _requestManager.GetResponseAsync(request.Id, TaskCreationOptions.RunContinuationsAsynchronously, CancellationToken.None).ConfigureAwait(false);

    // TODO: Process that result and return it
    _logger.LogInformation("Message: {Message}", Encoding.UTF8.GetString(responseTask.Span));
}

public async Task SubscribeToTickerAsync(Func<BookPrice, ValueTask> handler)
{
    ArgumentNullException.ThrowIfNull(handler);

    var request = new SocketRequest("SUBSCRIBE", new[] { "btcusdt@bookTicker" }, GetNextRequestId());
    await SendAsync(request).ConfigureAwait(false);

    if (!_subscriptionManager.AddSubscription(new Subscription<SocketRequest, Notification>("btcusdt@bookTicker", request, n => handler(n.Data.Deserialize<BookPrice>()!))))
    {
        _logger.LogDebug("Could not subscribe");
    }

    _logger.LogDebug("Successfully subscribed");
}

public async Task UnsubscribeTickerAsync()
{
    var request = new SocketRequest("UNSUBSCRIBE", new[] { "btcusdt@bookTicker" }, GetNextRequestId());
    await SendAsync(request).ConfigureAwait(false);

    if (!_subscriptionManager.RemoveSubscription("btcusdt@bookTicker"))
    {
        _logger.LogDebug("Could not unsubscribe");
    }

    _logger.LogDebug("Successfully unsubscribed");
}

Does that Task.Run count as multiple await?

private void OnConnected(object? sender, EventArgs e)
{
    _logger.LogTrace("Connected");

    _keepAliveTimer?.Start();

    foreach (var subscription in _subscriptionManager.GetSubscriptions())
    {
        Task.Run(async () =>
        {
            await SendAsync(subscription.Request).ConfigureAwait(false);
        });
    }
}
0

There are 0 best solutions below