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

Posted

技术标签:

【中文标题】将取消令牌传递给异步流的正确方法是啥?【英文标题】:What is the correct way to pass a cancellation token to an async stream?将取消令牌传递给异步流的正确方法是什么? 【发布时间】:2021-03-16 06:01:51 【问题描述】:

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

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

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

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

CA2016:将 CancellationToken 参数转发给接受一个的方法

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

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 ....

我所有的异步流方法都有一个 CancellationToken 的 default 值,这使它们成为可选参数。我认为我的问题的一部分可能是WithCancellation() 方法适用于您已经拥有IAsyncStream&lt;T&gt; 但不一定将取消令牌传递给它的用例。但这并不完全有意义,而且感觉我要么过于频繁地传递取消令牌,要么不够(或者在我应该做另一个的时候做错了其中一个。)

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

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

【问题讨论】:

这似乎是一个错误。你可能在使用 resharper 吗? 不,没有 ReSharper 或任何东西。只是 Visual Studio 企业版。我确实想知道警告是否还没有跟上新功能。 您可能想要清理您的解决方案,或清除您的 bin 和 obj 目录并重新启动。似乎静态分析在其引擎盖中有一些不应该的事情(至少在某些情况下)。 相关:What's the difference between returning AsyncEnumerable with EnumeratorCancellation or looping WithCancellation 和 Iterating an IAsyncEnumerable in a function returning an IAsyncEnumerable with cancellation @MikeD。 Stephen Cleary 就 Async Streams 做了一个非常简洁的演示。在他的演讲中,他有几个关于异步流取消的演示。 Here you can find it 【参考方案1】:

根据specification:

主要有两种消费场景:

    await foreach (var i in GetData(token)) ... 消费者调用异步迭代器方法, await foreach (var i in givenIAsyncEnumerable.WithCancellation(token)) ... 消费者处理给定的 IAsyncEnumerable 实例。

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

WithCancellation 适用于案例 #2。例如,有些库类型带有属性或方法,它返回IAsyncEnumerable&lt;T&gt;不允许直接传递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))


【讨论】:

以上是关于将取消令牌传递给异步流的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在 useEffect 挂钩中取消所有异步/等待任务以防止反应中的内存泄漏的正确方法是啥?

在Android中获取FCM消息令牌的正确方法是啥?

将表单元素状态传递给兄弟/父元素的正确方法是啥?

将“this”传递给新对象的内联重写方法的正确方法是啥? [复制]

在Android Kotlin中,将onclick事件传递给viewholder的正确方法是啥?

使用 APIRequestFactory 测试基于令牌的身份验证的正确方法是啥?