Как добавить индикатор прогресса в этот параллельный код - PullRequest
2 голосов
/ 17 июня 2019

Как добавить индикатор выполнения в третий цикл, показанный ниже?

Экспериментальные данные показали, что из следующих трех циклов третий является самым быстрым.Наилучшая производительность достигается, когда число потоков совпадает с логическими процессорами ЦП.Я думаю, что это связано с уменьшением времени, затрачиваемого на выделение и освобождение ресурсов потоков.

Это код для создания карты шума.Иногда время обработки достаточно велико, чтобы потребовался индикатор выполнения.

        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 Я прибыл сюда, и это почти работает.Проблема в том, что индикатор выполнения обновляется после того, как параллельный.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 ]

1 голос
/ 17 июня 2019

вы можете взглянуть на IProgress на MSDN. IProgress был представлен как стандартный способ отображения прогресса. Этот интерфейс предоставляет метод Report (T), который асинхронная задача вызывает, чтобы сообщить о прогрессе. Вы предоставляете этот интерфейс в сигнатуре асинхронного метода, и вызывающая сторона должна предоставить объект, который реализует этот интерфейс.

EDIT:

То, что потоки должны сообщать, зависит от того, насколько детализированы ваши отчеты. Самый простой подход - сообщать о прогрессе после каждой итерации . Я специально пишу итерацию, потому что метод 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 самостоятельно.

0 голосов
/ 18 июня 2019

Это не связано с основным вопросом (индикатор выполнения).Я просто хочу отметить, что .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);
    }
});
...