将取消令牌传递给异步流的正确方法是什么?

What is the correct way to pass a cancellation token to an async stream?

一段时间以来,我一直在努力了解 C# 用于异步代码的整个 async/await 模型。添加异步流(IAsyncEnumerable<T> 类型)看起来真的很酷,尤其是对于我正在编写的一些代码。

创建异步方法时的最佳做法是包含一个 CancellationToken 参数并将其用于取消异步进程。 (理想情况下,将其传递给方法中使用的底层异步方法调用。)

在创建 returns 异步流(IAsyncEnumerable<T>)的方法时,文档指出您的 CancellationToken 参数应使用 [EnumeratorCancellation] 属性修饰,然后使用 IAsyncEnumerable<T> 本身的 .WithCancellation() 方法传递的令牌。

但是,我一定是做错了什么,因为这仍然会触发警告:

CA2016: Forward the CancellationToken parameter to methods that take one

无论我是否以更标准的方式执行此操作,都会出现此警告:

async IAsyncEnumerable<aThingo> GetFlibbityStream([EnumeratorCancellation] CancellationToken cancellationToken = default) {
    aThingo slowValue = null;
    do {
        aThingo slowValue = await GetThatThingo(cancellationToken);
        yield return slowValue;
    while (slowValue != null);
}


async Task DoingStuff(CancellationToken cancellationToken) {
    await foreach(var thng in ThingStreamCreator.GetFlibbityStream().WithCancellation(cancellationToken)) {
        CrushThatThing(thng);
    }
}

或者在我需要获取 AsyncEnumerator 本身的地方(因为我需要一起遍历两个异步流,但不一定以相同的速率。)

async Task ValidatingThingsAsync(CancellationToken cancellationToken) {
    await using IAsyncEnumerator<aThingo> srcEnumerator = source.ThingValidityAsyncStream(dateCutOff).GetAsyncEnumerator(cancellationToken);
    ... streamy stuff ....
}

我所有的异步流方法都有一个 default 的 CancellationToken 值,这使它们成为可选参数。我认为我的问题的一部分可能是 WithCancellation() 方法适用于您已经拥有 IAsyncStream<T> 但不一定将取消令牌传递给它的用例。但这并不完全有意义,感觉就像我传递取消令牌的频率太高或不够(或者在我应该做另一个的时候做错了一个。)

在这些情况下,我应该直接将取消标记直接传递给异步流方法时,我是否只是通过不必要地传递取消标记来滥用 WithCancellation()GetAsyncEnumerator()

基本上我不应该使用 WithCancellation() 并且我不应该将任何东西传递给 GetAsyncEnumerator() 而是应该在我的异步流方法上删除 CancellationToken 的默认值并直接传递令牌给他们。基本上我认为我对将 CancellationToken 传递给异步流并确定当时使用的正确方法的不同方法的数量感到困惑...

根据 specification:

There are two main consumption scenarios:

  1. await foreach (var i in GetData(token)) ... where the consumer calls the async-iterator method,
  2. await foreach (var i in givenIAsyncEnumerable.WithCancellation(token)) ... where the consumer deals with a given IAsyncEnumerable instance.

您正在调用 GetFlibbityStream 方法,因此情况 #1。您应该将 CancellationToken 直接传递给该方法,并且 不应 GetFlibbityStreamWithCancellation 链接起来。否则CA2016的规则分析器会发出警告,它会是正确的。

WithCancellation 适用于案例 #2。比如有一些库类型带有属性或者方法,returnsIAsyncEnumerable<T>不允许直接传CancellationToken .

喜欢这个:

public interface IFlibbityService
{
    IAsyncEnumerable<aThingo> FlibbityStream { get; }
}

这仍然支持取消,但将令牌传递给 IFlibbityService.FlibbityStream 的唯一方法是使用 WithCancellation:

await foreach(var thng in flibbityService.FlibbityStream.WithCancellation(cancellationToken))
{
    // ...
}

回到你的代码,扔掉WithCancellation直接传token:

await foreach(var thng in ThingStreamCreator.GetFlibbityStream(cancellationToken))
{
}