如何:停止或中断 Parallel.For 循环

18 阅读3分钟

下面的示例演示如何从 For 循环中断(在 Visual Basic 中为 Exit),以及如何停止循环。
在此上下文中
“中断”表示完成当前线程上当前迭代之前的所有线程上的所有迭代,然后退出循环。
“停止”表示在方便的情况下尽快停止所有迭代。

示例

本示例演示 For 循环;但可以按同一方式停止或中断 ForEach 循环。 在 ForEach 循环中,以内部方式为每个分区中的每个元素生成一个迭代索引。

namespace StopOrBreak
{
    using System;
    using System.Collections.Concurrent;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    class Test
    {
        static void Main()
        {
            StopLoop();
            BreakAtThreshold();

            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }

        private static void StopLoop()
        {
            Console.WriteLine("Stop loop...");
            double[] source = MakeDemoSource(1000, 1);
            ConcurrentStack<double> results = new ConcurrentStack<double>();

            // i 是迭代变量。loopState 是一个编译器生成的 ParallelLoopState
            Parallel.For(0, source.Length, (i, loopState) =>
            {
                // 取从源中的任何位置检索的前 100 个值。
                if (i < 100)
                {
                    double d = Compute(source[i]);
                    results.Push(d);
                }
                else
                {
                    loopState.Stop();
                    return;
                }

            } // Close lambda expression.
            ); // Close Parallel.For

            Console.WriteLine("Results contains {0} elements", results.Count());
        }

        //在阔值处中断
        static void BreakAtThreshold()
        {
            double[] source = MakeDemoSource(10000, 1.0002);
            ConcurrentStack<double> results = new ConcurrentStack<double>();

            // 将所有值存储在指定阈值以下
            Parallel.For(0, source.Length, (i, loopState) =>
            {
                double d = Compute(source[i]);
                results.Push(d);
                if (d > .2)
                {
                    // 可能会被多次调用!
                    loopState.Break();
                    Console.WriteLine("Break called at iteration {0}. d = {1} ", i, d);
                    Thread.Sleep(1000);
                }
            });

            Console.WriteLine("results contains {0} elements", results.Count());
        }

        static double Compute(double d)
        {
            //让处理器稍微工作一下。
            return Math.Sqrt(d);
        }


        // 出于演示目的,创建一个单调递增值的人工数组。
        static double[] MakeDemoSource(int size, double valToFind)
        {
            double[] result = new double[size];
            double initialval = .01;
            for (int i = 0; i < size; i++)
            {
                initialval *= valToFind;
                result[i] = initialval;
            }

            return result;
        }
    }

}

在 Parallel.For 或 Parallel.ForEach 循环中,不能使用与顺序循环中相同的 break 或 Exit 语句,这是因为这些语言构造对于循环是有效的,而并行“循环”实际上是方法,不是循环。 相反,可以使用 Stop 或 Break 方法。 Parallel.For 的一些重载接受 Action<int, ParallelLoopState>(在 Visual Basic 中为 Action(Of Integer, ParallelLoopState))作为输入参数。 ParallelLoopState 对象由运行时在后台创建,您可以在 lambda 表达式中为它指定您喜欢的任何名称。

在下面的示例中,该方法只需要源序列中的 100 个值,检索出哪些元素并不重要。 在此案例中,使用 Stop 方法,因为它将告知循环的所有迭代(包括那些在其他线程上的当前迭代之前开始的迭代)在方便的情况下尽快停止。

在第二个方法中,将检索所有元素,直到源序列中指定的索引。 在此案例中,调用 Break,这是因为当到达一个线程上的索引时,源中前面的元素有可能尚未处理。 中断会导致其他线程放弃对后续片段的工作(如果它们正忙于任何这样的工作),并在退出循环之前处理完所有以前的元素。

在调用 Stop 或 Break 后,循环中的其他线程可能会继续运行一段时间(这不受应用程序开发人员的控制),理解这一点很重要。 可以使用 ParallelLoopState.IsStopped 属性检查是否已在另一个线程上停止该循环。 在下面的示例中,如果 IsStopped 为 true,则不会再有数据写入到集合中。

学习的.NET官方的教程,做个记录,加深学习吧,不过Stop()和Break()方法还是不能理解有什么区别