我有以下方法,可从http流中读取csv文档
public async IAsyncEnumerable<Line> GetLines([EnumeratorCancellation] CancellationToken cancellationToken)
{
HttpResponseMessage response = GetResponse();
using var responseStream = await response.Content.ReadAsStreamAsync();
using var streamReader = new StreamReader(responseStream);
using var csvReader = new CsvReader(streamReader);
while (!cancellationToken.IsCancellationRequested && await csvReader.ReadAsync())
{
yield return csvReader.GetRecord<Line>();
}
}
以及其他使用结果的方法
var documentAsyncEnumerable = graphClient.GetLines(cancellationToken);
await foreach (var document in documentAsyncEnumerable.WithCancellation(cancellationToken))
{
// Do something with document
}
我的问题是我是否应该只在一个地方使用取消令牌?取消令牌应该在产生记录之前执行,还是IAsyncEnumerable.WithCancellation()基本在做相同的事情?有什么区别?
答案 0 :(得分:3)
根据sources
,在后台,取消令牌仍然传递给GetAsyncEnumerator
方法
namespace System.Collections.Generic
{
public interface IAsyncEnumerable<out T>
{
IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);
}
public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
ValueTask<bool> MoveNextAsync();
T Current { get; }
}
}
您应该只使用一次cancellationToken
,直接传递或使用WithCancellation
,这些方法都是一样的。 WithCancellation
是IAsyncEnumerable<T>
的扩展方法,接受CancellationToken
作为参数(它与ConfigureAwait
使用相同的模式)。如果是[EnumeratorCancellation]
,编译器会生成将令牌传递给GetAsyncEnumerator
方法的代码
为什么有两种不同的方法呢?将令牌直接传递给 方法更简单,但是当您随意处理 IAsyncEnumerable来自其他来源,但仍然希望能够 要求取消构成它的所有内容。在 在极端情况下,将令牌传递给 GetAsyncEnumerator这样做可以避免“烧毁”令牌中的令牌。 单个枚举将被多次枚举的情况: 将其传递给GetAsyncEnumerator,每个传递一个不同的令牌 时间。
答案 1 :(得分:1)
我的问题是我应该只在一个地方使用取消令牌吗?
取消是合作的,因此为了能够取消,您必须在生产者代码{{1}中实施取消},提供GetLines
。因此,生产者是一个地方。
现在,假设将处理该数据的代码命名为IAsyncEnumerable<Line>
的方法,假设它是一个消费者。在您的情况下,它可能是一个代码库,但通常来说,它可能是另一个库,另一个存储库,另一个代码库。
在其他代码库中,不能保证它们具有相同的 ConsumeLines
。
那么,消费者如何取消?
消费者需要将CancellationToken
传递给CancellationToken
,但是如果您使用的是IAsyncEnumerable<T>.GetAsyncEnumerator
结构,则不会直接暴露它。
为解决此问题,添加了await foreach
扩展方法。只需将传递给它的WithCancellation
包装在ConfiguredCancelableAsyncEnumerable中,就可以将传递给它的CancellationToken
转发到基础IAsyncEnumerable
。
根据几个条件,此CancellationToken
使用CreateLinkedTokenSource链接到生产者中的一个,以便消费者可以使用生产者中实施的协作取消来取消,这样我们不仅可以取消消费,也可以制作。
应该在产生记录之前对取消令牌进行操作,还是IAsyncEnumerable.WithCancellation()基本上在做相同的事情?有什么区别?
是,您应该通过在生产者代码中使用IsCancellationRequested或ThrowIfCancellationRequested对CancellationToken
进行操作。取消是协作的,如果不在 producer 中实现,则将无法取消 production 的IAsyncEnumerable
值。
关于何时完全取消-在放弃之前或之后-完全由您决定,想法是避免任何不必要的工作。本着这种精神,您还可以在方法的第一行中检查取消,以避免发送不必要的http请求。
请记住,取消值的使用不一定与取消值的生成相同。
同样,生产者和消费者可能使用不同的代码库,并且可能使用来自不同CancellationTokens
的{{1}}。
要链接那些不同的 CancellationTokenSources
,您需要一起使用 CancellationTokens
属性。