Click here to Skip to main content
15,904,351 members
Articles / Programming Languages / C#
Tip/Trick

Reporting Progress the Right Way

Rate me:
Please Sign up or sign in to vote.
4.60/5 (3 votes)
17 Sep 2016CPOL1 min read 9.9K   7   2
A tip for beginners on how to calculate the progress value when processing a large amount of data.

Introduction

This tip will explain how to calculate progress values correctly. It's not about C# tasks or async programming. Though the code is written in C#, this tip applies to any programming language.

Background

Let's assume we have a large amount of data to process and want to give the user some kind of feedback on the progress, like a progressbar in a user interface. If we have a couple of million data points to process, it doesn't make sense to update the progressbar in each step. For N data points, we want to report progress no more than PROGRESS_MAX times.

Method 1

You might find yourself writing code like this:

const int PROGRESS_MAX = 100;

int percent = 0;

// Avoid divide by zero for N = 0.
int total = Math.Max(1, N - 1);

for (int i = 0; i < N; i++)
{
    ProcessDataPoint(data[i]);

    if ((PROGRESS_MAX * i) / total != percent)
    {
        percent = (PROGRESS_MAX * i) / total;
        ProgressChanged(percent);
    }
}

This will work fine for small N and small PROGRESS_MAX, but for larger values, the multiplication in...

if ((PROGRESS_MAX * i) / total != percent)

...might overflow. Using uint or long can solve the problem, but there's a better solution, which is more efficient.

Method 2

The following code works fine if N is larger than PROGRESS_MAX:

const int PROGRESS_MAX = 100;

double step = N / (double)PROGRESS_MAX;
double current = 0.0;

for (int i = 0; i < N; i++)
{
    ProcessDataPoint(data[i]);

    if (i >= current)
    {
        current += step;
        ProgressChanged((int)((current / step) + 0.5));
    }
}

If N might be smaller than PROGRESS_MAX, the following code can be used:

const int PROGRESS_MAX = 100;

double step = N / (double)PROGRESS_MAX;
double current = 0.0;

for (int i = 0; i < N; i++)
{
    ProcessDataPoint(data[i]);

    if (i >= current)
    {
        current += step;

        if (step < 1.0)
        {
            ProgressChanged((int)(((i + 1) / step) + 0.5));
        }
        else
        {
            ProgressChanged((int)((current / step) + 0.5));
        }
    }
}

Benchmark

Though processing the data will usually dominate the runtime, here are the benchmark results of both methods:

Size Calls Method 1 Time Calls Method 2 Time
50 49 0ms 49 0ms
100 99 0ms 99 0ms
101 100 0ms 100 0ms
2100 100 0ms 100 0ms
5000000 100 87ms 100 6ms
11119999 100 198ms 100 14ms
211119999 104 3839ms 100 273ms

In the last row, you can see that method 1 would raise the progress changed event 104 times, though PROGRESS_MAX is set to 100. This is due to arithmetic overflow.

Benchmark code:

const int PROGRESS_MAX = 100;

private static void Test()
{
    int[] values = { 50, 100, 101, 2100, 5000000, 11119999, 211119999 };

    Console.WriteLine("        Size     Calls 1      Time 1     Calls 2      Time 2");

    foreach (var item in values)
    {
        Benchmark(item);
    }
}

private static void Benchmark(int N)
{
    var timer = new System.Diagnostics.Stopwatch();

    Console.Write("{0,12}", N);

    // Method 1
    int percent = 0, j = 0;
    int total = (N - 1);

    timer.Start();
    for (int i = 0; i < N; i++)
    {
        if ((PROGRESS_MAX * i) / total != percent)
        {
            j++;
            percent = (PROGRESS_MAX * i) / total;
        }
    }
    timer.Stop();

    Console.Write("{0,12}", j);
    Console.Write("{0,10}ms", timer.ElapsedMilliseconds);

    j = 0;

    // Method 2
    double step = N / (double)PROGRESS_MAX;
    double current = 0.0;

    timer.Restart();
    for (int i = 0; i < N; i++)
    {
        if (i > current)
        {
            j++;
            current += step;
        }
    }
    timer.Stop();

    Console.Write("{0,12}", j);
    Console.Write("{0,10}ms", timer.ElapsedMilliseconds);
    Console.WriteLine();
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
Studied math and computer science at the university of Dortmund.

MCTS .NET Framework 4, Windows Applications

Comments and Discussions

 
QuestionNice article Pin
Emily Heiner25-Sep-16 13:01
Emily Heiner25-Sep-16 13:01 
I personally prefer to use time-based updating (progress update every second or so). This works very well, too!
QuestionNice work Pin
Garth J Lancaster18-Sep-16 15:40
professionalGarth J Lancaster18-Sep-16 15:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.