如何向这个并行代码添加进度条?

3

如何向下面所示的第三个循环添加进度条?

实验数据表明,以下三个循环中第三个是最快的。当线程数与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次,进度似乎会排队,并在方法返回后继续进行。调用该方法后抑制显示进度1秒钟足以解决问题。当方法执行时间很长时,进度条才真正有用,否则您可能会想知道是否正在执行任何操作。
完整项目请访问https://github.com/David-Marsh/Designer

进度条在计算过程中没有更新的原因可能是因为您正在UI线程中运行计算。您可能需要将计算移动到后台线程中。 - Theodor Zoulias
2个回答

2
您可能希望查看MSDN上的IProgress。IProgress被引入作为一种标准的显示进度的方式。该接口公开了Report(T)方法,异步任务调用该方法来报告进度。您在异步方法的签名中公开此接口,调用者必须提供实现此接口的对象。
编辑:
线程应该报告什么取决于您需要多细致的报告。最简单的方法是在每个迭代后报告进度。我故意写下“迭代”,因为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 类,或自己实现 IProgress 接口。

这很有帮助,让我思考了一下,但是在哪里调用Report(T)方法呢?有n个线程在分担工作。每个线程都应该报告进度吗?线程应该报告什么,总工作量还是线程工作量?在更新期间,报告的进度值是否需要锁定?使用进度条和Parallel.For循环的示例将非常好。到目前为止找到的所有示例代码都是针对单个后台线程的,实现起来并不太难。 - Neutone

0

这不涉及到主要的问题(进度条)。我只想指出,.NET Framework 包含一个Partitioner 类,因此没有必要手动分区数据:

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);
    }
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接