我是否需要检查 Parallel.Foreach 中的 cancellationtoken

Do I need to check the cancellationtoken in Parallel.Foreach

这是 How to: Cancel a Parallel.For or ForEach Loop

的文档

我已经编写了一个使用该功能的方法:

static async Task SpreadCheer(CancellationToken cancellationToken)
{
    cancellationToken.ThrowIfCancellationRequested();
    var friends = await GetFriends();

    Parallel.ForEach(friends,
            new ParallelOptions
                { CancellationToken = cancellationToken},
            friend=>
            {
                SendCake(friend);
                // (*1)
            });
}

在文档中的示例中,在每个循环的最后一行,(*1)。它添加了一个 cancellationToken.ThrowIfCancellationRequested.

这让我很困惑,因为那我为什么要把它传递给并行选项。我也可以 see that 如果令牌被取消,Parallel.Foreach 将在处理项目后抛出。如果令牌从一开始就被取消,它还会在开始任何循环之前立即抛出。这也是我对 运行 和测试这段代码的经验,不需要明确抛出取消。

我需要(甚至应该)在位置 (*1) 中放置一个 cancellationToken.ThrowIfCancellationRequested 吗?

当然文档有它的示例,但它没有明确提及它,如果有一个单元测试涵盖这个,它不是通过该测试所需的代码。据我所知。


额外上下文:

有关取消的一些上下文。取消的原因是我有一个用于 SpreadCheer 的 Azure WebJob,传播欢呼可能需要一段时间,如果 Azure 向我发送关闭信号,我想优雅地停止传播欢呼。我不想弄乱任何人的蛋糕。

你应该在操作本身中使用 token.ThrowIfCancellationRequested() 的原因是,并行操作将继续执行直到最后。

Parallel.ForEach只检查操作开始和结束时的令牌,中间不检查。

因此,如果您有很多并行操作或长 运行 操作,您需要自己检查操作中的取消。

您可以使用以下代码段验证行为,有或没有 token.ThrowIfCancellationRequested()

internal class Program
{
    private static void Main()
    {
        var source = new CancellationTokenSource();

        var token = source.Token;

        Task.Run(() =>
        {
            Parallel.ForEach(Enumerable.Range(1, 100), new ParallelOptions {CancellationToken = token},
                i =>
                {
                    for (var y = 0; y < 100; y++)
                    {
                        token.ThrowIfCancellationRequested();
                        Thread.Sleep(1000);
                        Console.WriteLine($"{i} {y}");
                    }
                });
        }, token);

        Console.WriteLine("press return to cancel...");
        Console.ReadLine();

        source.Cancel();

        Console.WriteLine("press return to exit...");
        Console.ReadLine();
    }
}