1

据我了解, ( 和 ) 中的所有三个静态方法都在后台Parallel创建任务。ForForEachInvoke

您可以通过取消内部的令牌来停止创建这些任务ParallelOptions

我做了两个简单的例子。

在第一个中使用For第二个中的方法Invoke

在该For方法的情况下,行为是预期的,在取消令牌后,新任务的创建将停止。在该Invoke方法的情况下,这不会发生。无论我在 Invoke 方法中放了多少方法,它们总是在令牌取消后执行。我不明白为什么会这样。

docs.microsoft.com 的Parallel.Invoke方法中 有人说:

与结构一起传递的取消令牌ParallelOptions使调用者能够取消整个操作。

问题:为什么在 Invoke 方法的情况下,所有任务都已执行,而取消令牌则无济于事?或者也许我做错了什么,然后告诉我什么。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class MyClass
    {
        public int a = 0;
        public void Add()
        {
            lock (this)
            {
                Thread.Sleep(100);
                Console.WriteLine($"Do Add {DateTime.Now}, a={a}");
                a++;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            void MyMethodForCancel(CancellationTokenSource cancellationTokenSource)
            {
                Random random = new Random();

                while (true)
                {
                    if (random.Next(1, 100) == 50)
                    {
                        cancellationTokenSource.Cancel();
                        Console.WriteLine($"Cancel token {DateTime.Now}");
                        return;
                    }
                }
            }

            CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
            ParallelOptions parallelOptions = new ParallelOptions();
            parallelOptions.CancellationToken = cancellationTokenSource.Token;

            MyClass myClass2 = new MyClass();
            Action[] actions = new Action[50];

            for (int i = 0; i < actions.Length; i++)
            {
                actions[i] = myClass2.Add;
            }

            Task MyTask1 = Task.Run(() => Parallel.Invoke(parallelOptions, actions));
            Task MyTask2 = Task.Run(() => { Thread.Sleep(1); MyMethodForCancel(cancellationTokenSource); });

            try
            {
                Task.WaitAll(MyTask1, MyTask2);
            }
            catch
            {

            }

            Console.WriteLine($"a = {myClass2.a}"); //a = 50. Always.               
        }
    }
}
4

1 回答 1

2

这是我试图确认您的声明,即该Parallel.Invoke方法忽略CancellationToken传递给其选项的声明。我正在创建一个CancellationTokenSource在 700 毫秒后用计时器取消的,以及 20Action秒,每个需要 500 毫秒才能完成。让我们将这 20 个动作传递给Parallel.Invoke,看看会发生什么:

static class Program
{
    static void Main()
    {
        var cts = new CancellationTokenSource(700);
        cts.Token.Register(() => Print($"The Token was canceled."));
        var options = new ParallelOptions()
        {
            MaxDegreeOfParallelism = 2,
            CancellationToken = cts.Token
        };
        Print("Before starting the Parallel.Invoke.");
        try
        {
            Parallel.Invoke(options, Enumerable.Range(1, 20).Select(i => new Action(() =>
            {
                Print($"Running action #{i}");
                Thread.Sleep(500);
            })).ToArray());
        }
        catch (OperationCanceledException)
        {
            Print("The Parallel.Invoke was canceled.");
        }
    }

    static void Print(object value)
    {
        Console.WriteLine($@"{DateTime.Now:HH:mm:ss.fff} [{Thread.CurrentThread
            .ManagedThreadId}] > {value}");
    }
}

输出:

12:12:46.422 [1] > Before starting the Parallel.Invoke.
12:12:46.450 [1] > Running action #1
12:12:46.451 [5] > Running action #2
12:12:46.951 [1] > Running action #3
12:12:46.951 [5] > Running action #4
12:12:47.122 [7] > The Token was canceled.
12:12:47.458 [1] > The Parallel.Invoke was canceled.

在 Fiddle 上试试

似乎CancellationToken受到尊重。20 个动作中只执行了 4 个,取消令牌后不再调用任何动作。

但是请注意,我已经配置了Parallel.Invoke一个小的MaxDegreeOfParallelism. 这个很重要。相反,如果我将此选项配置为较大的值,例如 100,或者将其保留为默认值 -1(无界),则将调用所有 20 个操作。在这种情况下发生的ThreadPool饱和,这意味着所有可用ThreadPool线程都被该方法积极借用Parallel.Invoke,并且没有可用线程来做其他事情,例如调用CancellationTokenSource!的计划取消 所以取消被推迟到完成Parallel.Invoke,这显然为时已晚。

道德教训是:MaxDegreeOfParallelism在使用课程时始终配置选项Parallel。不要相信文档说:“一般情况下,您不需要修改此设置”。这是一个可怕的建议。如果您的意图是让您饿死,您应该只遵循此建议ThreadPool

于 2022-01-18T12:40:23.427 回答