如何将进度条添加到此并行代码中

How can a progress bar be added to this parallel code

下图第三个循环如何添加进度条?

实验证据表明,在以下三个循环中,第三个循环最快。最佳性能是当线程数与 CPU 的逻辑处理器数相同时。我认为这是由于减少了分配和释放线程资源所花费的时间。

这是生成噪声图的代码。有时处理时间长到需要进度条。

        for (int j = 0; j < data.Length; j++)
        {
            var x = (location.X + (j % width));
            var y = (location.Y + (j / width));
            Vector3 p = new Vector3(x, y, frame);
            p *= zoom;
            float val = noise.GetNoise(p);
            data[j] += val;
            min = Math.Min(min, val);
            max = Math.Max(max, val);
        }

        Parallel.For(0, data.Length, (i) => {
            var x = (location.X + (i % width));
            var y = (location.Y + (i / width));
            Vector3 p = new Vector3(x, y, frame);
            p *= zoom;
            float val = noise.GetNoise(p);
            data[i] += val;
            min = Math.Min(min, val);
            max = Math.Max(max, val);
        });


        Parallel.For(0, threads, (i) =>
        {
            int from = i * data.Length / threads;
            int to = from + data.Length / threads;
            if (i == threads - 1) to = data.Length - 1;
            for (int j = from; j < to; j++)
            {
                var x = (location.X + (j % width));
                var y = (location.Y + (j / width));
                Vector3 p = new Vector3(x, y, frame);
                p *= zoom;
                float val = noise.GetNoise(p);
                data[j] += val;
                min = Math.Min(min, val);
                max = Math.Max(max, val);
            }
        }
        );

进度条的更新速度限制在每秒几次,以免浪费时间画进度条太频繁。

添加 IProgress 我已经到了这里,这几乎可以工作了。问题是进度条在 parallel.for 完全完成后更新。

    private async Task<int> FillDataParallelAsync(int threads, IProgress<int> progress)
    {
        int precent = 0;
        /// parallel loop - easy and fast.
        Parallel.For(0, threads, (i) =>
        {
            int from = i * data.Length / threads;
            int to = from + data.Length / threads;
            if (i == threads - 1) to = data.Length - 1;
            for (int j = from; j < to; j++)
            {
                var x = (location.X + (j % width));
                var y = (location.Y + (j / width));
                Vector3 p = new Vector3(x, y, frame);
                p *= zoom;
                float val = noise.GetNoise(p);
                data[j] += val;
                min = Math.Min(min, val);
                max = Math.Max(max, val);

                if(j%(data.Length / 100) ==0)
                {
                    if (progress != null)
                    {
                        progress.Report(precent);
                    }
                    Interlocked.Increment(ref precent);
                }
            }
        }
        );
        return 0;
    }

经过长时间的研究,现在看起来像这样。

        private Boolean FillDataParallel3D(int threads, CancellationToken token, IProgress<int> progress)
    {
        int precent = 0;
        Vector3 imageCenter = location;
        imageCenter.X -= width / 2;
        imageCenter.Y -= height / 2;
        ParallelOptions options = new ParallelOptions { CancellationToken = token };
        /// parallel loop - easy and fast.
        try
        {
            ParallelLoopResult result =
            Parallel.For(0, threads, options, (i, loopState) =>
            {
                int from = i * data.Length / threads;
                int to = from + data.Length / threads;
                if (i == threads - 1) to = data.Length - 1;
                for (int j = from; j < to; j++)
                {
                    if (loopState.ShouldExitCurrentIteration) break;
                    Vector3 p = imageCenter;
                    p.X += (j % width);
                    p.Y += (j / width);
                    p *= zoom;
                    float val = noise.GetNoise(p);
                    data[j] += val;
                    min = Math.Min(min, val);
                    max = Math.Max(max, val);
                    if (j % (data.Length / 100) == 0)
                    {
                        try { if (progress != null) progress.Report(precent); }
                        catch { }
                        Interlocked.Increment(ref precent);
                    }
                }
            }
            );
            return result.IsCompleted;
        }
        catch { }
        return false;
    }

进度从每个线程开始递增,总计 100 次。它在更新进度条时仍然有一些延迟,但这似乎是不可避免的。例如,如果进度条在不到绘制 100 次更新的时间内递增 100 次,则进度似乎排队并在方法 returns 之后继续前进。在调用该方法后抑制进度显示一秒钟效果很好。进度条只有在该方法花费的时间太长以至于您想知道是否正在发生任何事情时才真正有用。

完整项目位于 https://github.com/David-Marsh/Designer

您可能想看看 MSDN 上的 IProgress。 IProgress 是作为显示进度的标准方式引入的。该接口公开了一个 Report(T) 方法,异步任务调用该方法来报告进度。您在异步方法的签名中公开此接口,调用者必须提供一个实现此接口的对象。

编辑:

线程应该报告什么取决于 fine-grained 您需要报告的方式。最简单的方法是在每次 迭代 后报告进度。我有意编写迭代,因为 Parallel.For 方法不一定在单独的线程上执行每个迭代。

您的进度(可能以百分比表示)是所有线程共享的。因此,以百分比计算当前进度并调用 Report 方法很可能需要锁定。请注意,这会对性能产生一些影响。

至于计算当前进度,你知道你有多少次迭代。您可以计算一次迭代与总工作量相比有多少。在每次迭代的开始或结束时,您只需将差异添加到整体进度中。

下面是一个可能会帮助您解决问题的示例:

public void ParallelForProgressExample(IProgress<int> progress = null)
{
    int percent = 0;
    ...

    var result = Parallel.For(fromInclusive, toExclusive, (i, state) => 
    {
        // do your work

        lock (lockObject)
        {
            // caluclate percentage
            // ...

            // update progress
            progress?.Report(percent);

        }
    });
}

作为进度,您可以使用 System.Progress class 或自己实现 IProgress 接口。

这与主要问题(进度条)无关。我只是想说明.NET Framework 包含一个Partitioner class,所以不需要手动分区数据:

Parallel.ForEach(Partitioner.Create(0, data.Length), range =>
{
    for (int j = range.Item1; j < range.Item2; j++)
    {
        var x = (location.X + (j % width));
        var y = (location.Y + (j / width));
        Vector3 p = new Vector3(x, y, frame);
        p *= zoom;
        float val = noise.GetNoise(p);
        data[j] += val;
        min = Math.Min(min, val);
        max = Math.Max(max, val);
    }
});