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

时间:2019-06-17 07:26:43

标签: c# progress-bar parallel.for

如何将进度条添加到下面显示的第三个循环中?

实验证据表明,在以下三个循环中,第三个循环最快。最好的性能是当线程数与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倍,则进度似乎已排队,并在方法返回后继续进行。调用该方法后,将进度显示抑制一秒钟的效果很好。进度条仅在该方法花费很长时间以至于您怀疑是否正在发生任何事情时才真正有用。

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

2 个答案:

答案 0 :(得分:1)

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

编辑:

线程应报告的内容取决于您需要报告的粒度。最简单的方法是在每次迭代之后报告进度。我故意编写迭代,因为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接口。

答案 1 :(得分: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);
    }
});