.NET 6 Parallel.ForEachAsync 中需要两个令牌吗?

The need for two tokens in .NET 6 Parallel.ForEachAsync?

我正在试验如何跳出 ForEachAsync 循环。 break 不起作用,但我可以在 CancellationTokenSource 上调用 CancelForEachAsync 的签名有两个标记 - 一个作为独立参数,另一个在 Func 正文签名中。

我注意到调用 cts.Cancel() 时,tokent 变量都将 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,它将被取消:

  • 关于“外部”cancelation(这就是为什么在调用 cts.Cancel() 时您会看到 t.IsCancellationRequested 设置为 true
  • 出于内部原因(我发现的原因 - 任何迭代 has thrown an uncaught exception)。

所以传递给 Parallel.ForEachAsynccancellationToken CancellationToken 参数的目的是支持调用者取消和传递给它调用的异步委托的取消 - 支持外部(即调用者)取消) 和内部资源(参见 P.S.).

P.S.

另请注意,通常在您的方法中传递和检查令牌状态是个好主意(即 await someConditionAsync(t) 以及内部相应的实现),因为 CancelationToken 用于所谓的 cooperative cancelation.

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 意味着您可以 'inherit' lambda 之外的变量,但这并不意味着您应该这样做。