从 PLINQ Select 块中突破 (C#)

Breaking out from PLINQ Select Block (C#)

有什么方法可以从 PLINQ 的 SELECT 块中突破吗?基于 SELECT 块中的条件,我想打破循环。到目前为止,我看到了各种使用取消令牌从循环外部中断 PLINQ 的示例。但是,我的问题是从循环内部中断。

这是我的代码:

           var simulationResults =
                Enumerable.Range(0, 10000)
                    .AsParallel()
                    .WithCancellation(token ?? CancellationToken.None)
                    .Select(z =>
                    {
                        progressAction?.Invoke();

                        double aResult = someMethod()

                        if (double.IsNaN(aResult))
                        {
                            // todo: Find a way to Break the Parallel loop.
                        }

                        return aResult;
                    });

如果没有可用的技术,也许我需要找到一种困难的方法,比如使用带有 volatile 关键字的布尔标志。我将在 PLINQ Select 块中使用 lock 语句设置该标志,然后下一次,它将读取该标志并确定它是否应该中断。但是,不确定这是否是一个好的解决方案。此外,我非常不愿意在 PLINQ 内部使用锁,这违背了并行计算的目的。使用 Lock,我可能会强制执行同步而无法获得并行编程的好处。

那么有更好的主意吗?

我建议你使用 Parallel.For 而不是 PLINQ:

Why not PLINQ?

Even though PLINQ provides support for exiting from within a query execution, the differences between PLINQ’s exit mechanism and Parallel.ForEach are substantial. To exit from a PLINQ query, supply the query with a cancellation token, as explained here. With Parallel.ForEach, exiting flags are polled on every iteration. In PLINQ, the cancellation token is polled once every so often, so you cannot assume that a cancelled query will stop quickly.

取自退出操作部分When To Use Parallel.ForEach and When to Use PLINQ

在上面的文章中,您可以找到有关如何使用 ParallelLoopStateStopBreak 方法停止 Parallel.ForEach/Parallel.For 的详细说明,这些方法是那里有完整的描述 here:

In this context, "break" means complete all iterations on all threads that are prior to the current iteration on the current thread, and then exit the loop. "Stop" means to stop all iterations as soon as convenient.

使用 Parallel.For 你的代码应该是这样的:

var simulationResults = new ConcurrentBag<double>();
Parallel.For(0, 10000, (i, loopState) =>
{
    //I assume this method is thread safe
    progressAction?.Invoke();
    double aResult = someMethod()
    if (double.IsNaN(aResult))
    {
        //or loopState.Break();
        loopState.Stop();
    }
    simulationResults.Add(aResult);
});

使用 Parallel.For 而不是 PLINQ 的一个缺点是当您需要保留输入数据顺序时。
使用 PLINQ 您可以使用 AsOrdered 方法轻松完成,而使用 Parallel.For 则要困难得多(但仍有可能,请参阅链接文章以获取详细信息)。
但是你没有提到这个要求,所以我在提供的代码中忽略了它。

如果你想在第一个不是数字的地方换行,使用TakeWhile。如果你只是想跳过它们,你可以使用 Where.

       var simulationResults =
            Enumerable.Range(0, 10000)
                .AsParallel()
                .WithCancellation(token ?? CancellationToken.None)
                .Select(z =>
                {
                    progressAction?.Invoke();
                    return someMethod();
                })
                .TakeWhile(result => !double.IsNaN(result));