我正在试验如何突破ForEachAsync
循环。break
不起作用,但我可以在CancellationTokenSource上调用Cancel
。ForEachAsync
的签名有两个令牌——一个作为独立参数,另一个在Func
主体签名中。
我注意到,当调用cts.Cancel()
时,token
和t
变量都将IsCancellationRequested
设置为true。所以,我的问题是:两个独立的token
论点的目的是什么?有什么区别值得注意吗?
List<string> symbols = new() { "A", "B", "C" };
var cts = new CancellationTokenSource();
var token = cts.Token;
token.ThrowIfCancellationRequested();
try
{
await Parallel.ForEachAsync(symbols, token, async (symbol, t) =>
{
if (await someConditionAsync())
{
cts.Cancel();
}
});
catch (OperationCanceledException oce)
{
Console.WriteLine($"Stopping parallel loop: {oce}");
}
finally
{
cts.Dispose();
}
传递给ForEachAsync
调用的方法主体的令牌不同,并且来自将被取消的内部CancellationTokenSource
:
- 在";外部";cancellation(这就是为什么在调用
cts.Cancel()
时会看到t.IsCancellationRequested
设置为true
( - 由于内部原因(我发现,任何迭代都抛出了一个未捕获的异常,即应用了故障快速原则(
因此,传递给Parallel.ForEachAsync
的cancellationToken CancellationToken
参数的目的是支持调用者的取消,以及传递给它调用的异步委托的参数——支持外部(即调用者(和内部源的取消(请参阅p.S.(
p.S.
还要注意的是,通常在方法中传递和检查令牌状态是一个好主意(即,内部有相应实现的await someConditionAsync(t)
(,因为CancelationToken
用于所谓的协作取消。
Parallel.ForEachAsync
获取一个令牌,您可以使用该令牌来取消for each(函数的输入(,该令牌还传递给for each的每次迭代(lambda的输入(。
将取消标记传递给lambda的原因之一是避免捕获lambda表达式之外的变量。
想象一下这个代码:
await Parallel.ForEachAsync(symbols, token, async (symbol, t) => MyCode(symbol, t));
Task async MyCode(string symbol, CancellationToken token)
{
if (await someConditionAsync())
{
cts.Cancel();
}
});
以这种方式编写,MyCode
无法访问token
。
使用lambda意味着您可以在lambda之外"继承"变量,但这并不意味着您应该这样做。