我有以下方法可以从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.WithCancel(( 基本上在做同样的事情?如果有的话有什么区别?
根据消息来源,在引擎盖下,取消令牌无论如何都会传递给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
方法
的代码MSDN杂志中描述了两种不同方式的原因
为什么有两种不同的方法?将令牌直接传递给 方法更容易,但当你被递给任意时它不起作用 IAsyncEnumerable从其他来源,但仍希望能够 要求取消组成它的所有内容。在 在极端情况下,将令牌传递给 GetAsyncEnumerator,因为这样做可以避免"刻录"令牌 单个可枚举对象将被多次枚举的情况:通过 将其传递给 GetAsyncEnumerator,每个都可以传递不同的令牌 时间。
我的问题是我应该只在一个地方使用取消令牌吗?
取消是合作的,因此为了能够取消,您必须在提供IAsyncEnumerable<Line>
的生产者代码GetLines
中实现取消。所以,生产者是一个地方。
现在,假设使用该数据执行某些操作的代码名为ConsumeLines
的方法,假设它是一个使用者。在您的情况下,它可能是一个代码库,但一般来说,它可以是另一个库、另一个存储库、另一个代码库。
在另一个代码库中,不能保证它们具有相同的CancellationToken
。
那么,消费者如何取消呢?
使用者需要将CancellationToken
传递给IAsyncEnumerable<T>.GetAsyncEnumerator
,但如果使用的是构造await foreach
它不会直接公开。
为了解决这个问题,添加了WithCancellation
扩展方法。它只是通过将传递给它的CancellationToken
转发到基础IAsyncEnumerable
,方法是将其包装在 ConfigurationCancelableAsyncEnumerable 中。
根据几个条件,此CancellationToken
使用 CreateLinkedTokenSource 链接到生产者中的,以便消费者可以使用生产者中实现的合作取消取消,因此我们不仅可以取消消费,还可以取消生产。
应该在生成记录之前对取消令牌进行操作,还是 IAsyncEnumerable.WithCancel(( 基本上在做同样的事情?如果有的话有什么区别?
是的,您应该通过在生产者代码中使用 IsCancelRequest 或 ThrowIfCancelRequest 来对CancellationToken
进行操作。取消是合作的,如果不在生产者中实现它,您将无法取消生成IAsyncEnumerable
的值。
至于何时取消 - 在屈服之前或之后 - 完全取决于您,这个想法是避免任何不必要的工作。本着这种精神,您还可以在方法的第一行检查取消,以避免发送不必要的 http 请求。
请记住,取消值的使用不一定与取消值的产生相同。
同样,生产者和消费者可能位于不同的代码库中,并且可以使用来自不同CancellationTokenSources
CancellationTokens
。
若要将这些不同的CancellationTokens
链接在一起,需要使用EnumeratorCancellation
属性。
请阅读我的文章 EnumeratorCancel: CancelToken 参数中生成的 IAsyncEnumerable.GetAsyncEnumerator 将未使用
。