在使用 ChannelWriter 的 SignalR Core 中:如果出现异常,是否需要调用 TryComplete 两次?

In SignalR Core using ChannelWriter: Do I need to call TryComplete twice if there's an exception?

以下是 Microsoft Use streaming in ASP.NET Core SignalR 文章的摘录:

private async Task WriteItemsAsync(
    ChannelWriter<int> writer,
    int count,
    int delay,
    CancellationToken cancellationToken)
{
    try
    {
        for (var i = 0; i < count; i++)
        {
            // Check the cancellation token regularly so that the server will stop
            // producing items if the client disconnects.
            cancellationToken.ThrowIfCancellationRequested();
            await writer.WriteAsync(i);

            // Use the cancellationToken in other APIs that accept cancellation
            // tokens so the cancellation can flow down to them.
            await Task.Delay(delay, cancellationToken);
        }
    }
    catch (Exception ex)
    {
        writer.TryComplete(ex);
    }

    writer.TryComplete();
}

如果有异常,会先调用writer.TryComplete(ex),然后调用writer.TryComplete()。换句话说,它两次调用 TryComplete(尽管重载不同)。

有这个必要吗?我应该在 writer.TryComplete(ex) 之后添加一个 return 语句以避免调用它两次吗?还是第二个 writer.TryComplete() 在调用前一个之后起到了一些有意义的作用?

没必要。这不是频道制作人的最佳例子。 writer.TryComplete() 应该是 try{} 块的最后一次调用:

private async Task WriteItemsAsync(
    ChannelWriter<int> writer,
    int count,
    int delay,
    CancellationToken cancellationToken)
{
    try
    {
        for (var i = 0; i < count; i++)
        {
            // Check the cancellation token regularly so that the server will stop
            // producing items if the client disconnects.
            cancellationToken.ThrowIfCancellationRequested();
            await writer.WriteAsync(i);

            // Use the cancellationToken in other APIs that accept cancellation
            // tokens so the cancellation can flow down to them.
            await Task.Delay(delay, cancellationToken);
        }
        writer.TryComplete();
    }
    catch (Exception ex)
    {
        writer.TryComplete(ex);
    }   
}

这样,它只被调用一次,无论是在循环成功终止时,还是在由于任何原因抛出异常时。

您可以简单地跳出循环,而不是使用 ThrowIfCancellationRequested 取消取消:

for (var i = 0; i < count; i++)
{
    if (cancellationToken.IsCancellationRequested)
    {
        break;
    }
    await writer.WriteAsync(i);
    await Task.Delay(delay, cancellationToken);
}
writer.TryComplete();

在有界频道的情况下,WriteAsync也应该收到取消令牌,否则如果频道已满可能会卡住:

await writer.WriteAsync(i,cancellationToken);