MoonKnight
MoonKnight

Reputation: 23831

Reporting from Background Thread Using IProgress<T>

All, I have an expensive task which I undertake using the async/await keywords under .NET4.5. I report progress from the background thread via the Progress<T> class.

The object I pass into IProgress<T> as the type is ProgressInfo which I create as a singleton class to avoid the overhead of creating and disposing of this type of object a great number of times during a run. The class is

public class ProgressInfo : IDisposable
{
    public static ProgressInfo instance = null;
    public WorkbookProgressInfo workbookinfo { get; set; }
    public string progressMessage { get; set; }
    public int progressPercentage { get; set; }

    // Default constructor which has a few overloads 
    // for the class members above.
    protected ProgressInfo()
    {
    }
    ...

    // The default instance creator for the class. 
    // This also has overloads to allow changes to the 
    // class variables.
    public static ProgressInfo Instance()
    {
        if (instance == null)
            instance = new ProgressInfo();
        return instance;
    }
    ...
}

I report the progress via the method ReportProgress and set up my IProgress<ProgressInfo> as

IProgress<CostEngine.ProgressInfo> progressIndicator =
    new Progress<CostEngine.ProgressInfo>(ReportProgress);

and reporting from the background thread is typically done using a global ProgressInfo progressInfo and global IProgress<ProgressInfo> progressIndicator like

...
progressInfo = new ProgressInfo.Instance(workbookinfo, message, n);
progressIndicator.Report(progressInfo);
...

The problem is that for runs that are small and execute quickly the ProgressInfo object passed in to ReportProgress changes as ReportProgress is executing, so I test

if (progressInfo.workbookinfo != null)
{
    // Do stuff <- here progressInfo.workbookinfo is changing to null!
}

How can I avoid this issue whilst keeping the expense of reporting the progress at a minimum?

Thanks for your time.

Upvotes: 1

Views: 1297

Answers (1)

Daniel Hilgarth
Daniel Hilgarth

Reputation: 174457

You are trying to optimize the wrong part of your application here.

Several thousand objects are nothing for an application. Creating them most likely won't be a performance problem in your application. However, what could be a problem is the updating of the UI. If those thousands of progress reports are happening in a short period of time, you will update your UI constantly. Updating the UI costs time, so if you will run into a bottle neck, it will be here.

The correct approach here is to only report what you need to report.
For example, when you have 60.000 iterations that happen in - let's say - a minute and you report every single one of these iterations, you will try to update the UI every millisecond.
Do you really need 1000 FPS for your progress bar? I doubt it.
It would be perfectly good to only report every second. However, reporting every second creates it's own kind of overhead.

What I am doing in my applications is to report as soon as the percentage changed.
That would result in a maximum of 100 reports, no matter how many iterations you actually have.
In the above example I would report every 600 iterations.

To simplify this, I actually created a class called ProgressReporter.
In its constructor, it takes a factory delegate that creates the ProgressInfo object and the total number of iterations.
It provides a ReportProgress method you pass in the current iteration.
The class internally stores the previously reported percentage and when calling this method it calculates the new percentage. Only if the differ will it use the factory delegate to create the new ProgressInfo and call Report on the IProgress<T> instance.

BTW: If your background operation doesn't take minutes but hours, you can change this to not report when the percentage changed but when the first or second decimal of the percentage changed.
The point here is: Find an amount of reports that gives the user a feedback every few seconds.

Upvotes: 4

Related Questions