Click here to Skip to main content
15,883,908 members
Articles / Desktop Programming / Win32
Article

Background Cycle Timing

Rate me:
Please Sign up or sign in to vote.
4.80/5 (6 votes)
20 Aug 2014CPOL2 min read 20.4K   139   18   13
High Resolution Timer EventArg Class for timing background tasks.

Introduction

I so often have to monitor what's happening in a background process and got tired of re-writing the code for this in so many ways. So I figured I'd share what I have with you, especially in the light of the fact that I've gotten so many helpful code snippets from you all. This is the first article I've submitted and have had a little trouble with the source formateeing ;). The source itself is formatted fine.

Background

Basically, the High Resolution Timing function is from Microsoft at: http://msdn.microsoft.com/en-us/library/aa964692(v=vs.85).aspx

That example was for Windows CE, so I changed the DllIMports to "kernel32.dll" instead of "coredll.dll" as in their example, and added various cycle timing/display methods.

Then I Inherited it into a very basic EventArg class such as is used with the BackgroundWorker class. So the timing details of my background processes would always be easily available as these args get passed around from EventHandler to EventHandler with slight variations.

Using the code

Except for the HiResTimer class, I've tried to keep it as simple as possible. The EventArg class that inherits it is very basic, but you could add what you want to it for specific purposes. The idea behind the HiResTimer class was to write it once and have high resolution timing details easily available and somewhat transparent to the actual work being done.

Outside of using the HiResTimer as a base for an EventArgs class, using the class in a stand-alone way is illustrated below:

C#
// add the ClassHighResTimer.cs file to your project
// add a StatusStrip with a label and a progress bar
// and a Button to your Form
// add these to your using statements
// using AbsoluteGenius;
// using System.Threading;
//
// then put this in as your button1_Click event code
private void button1_Click(object sender, EventArgs e)
{
    // this creates and inits cycle timing
    Utility.HiResTimer hiResTimer = new Utility.HiResTimer();
    Int64 totalCount = 1000; // loop counter total count
    // if there's a lot of processing before start
    // hiResTimer.StartTime = DateTime.Now; // (optional) will re-init cycle timing
    while (hiResTimer.CycleCount != totalCount - 1)
    { 
        //
        // do whatever work intended here
        Thread.Sleep(100);
        //
        hiResTimer.UpdateCycleStats(); // updates cycle stats and hiResTimer.CycleCount
        toolStripStatusLabel1.Text = " Run Time: " + hiResTimer.RunningTime 
                                   + " Avg Time: " + hiResTimer.CycleAvgTime
                                   + " Cycle Time: " + hiResTimer.CycleTime;
        toolStripProgressBar1.Value = hiResTimer.CyclePercentage(totalCount);
        statusStrip1.Refresh();
    }
    // do whatever post-loop processing (display/save data, etc.)
    // hiResTimer.EndTime = DateTime.Now; // (optional) logs completion of overall task
    toolStripStatusLabel1.Text = " Run Time: " + hiResTimer.RunningTime // the total while loop time
                               + " Avg Time: " + hiResTimer.CycleAvgTime// average while loop interval
                               + " Total Time: " + hiResTimer.TotalTime;// total task time 
                                                                        // (also logs completion and EndTime)
    // and that's all there is to it
}

The EventArgs class simply inherits this same functionality:

C#
public class HiResEventArgs : HiResTimer
{
    #region "Members"
    private object argument = null;
    public object RealResult = null;
    public object RealUserState = null;
    #endregion

    #region "Construction"
    public HiResEventArgs(object argument)
        : base() // base() inits the cycle timing
    {
        this.argument = argument; // pick up the argument
    }
    public static HiResEventArgs CopyOfHiResEventArgs(HiResEventArgs hiResEventArgs)
    {
        HiResEventArgs returnCopy = new HiResEventArgs(hiResEventArgs.argument);
        returnCopy.RealUserState = hiResEventArgs.RealUserState;
        returnCopy.RealResult = hiResEventArgs.RealResult;
        returnCopy.CopyHiResTimer(hiResEventArgs);
        return returnCopy;
    }
    #endregion

    #region "Properties"
    public object Argument
    {
        get { return argument; }
    }
    #endregion
}

So, for example, the above stand-alone code moved over to a DoWork function of a BackgroundWorker class would be very similar, except it would be using a HiResEventArgs variable as the argument sent to it. The button1_Click handler would create the HiResEventArgs object and send it to the DoWork function as the arguument/paramater to RunWorkerAsync.

C#
Utility.HiResEventArgs hiResArgs = new Utility.HiResEventArgs(100); // 100 loop total as argument
backgroundWorker1.RunWorkerAsync(hiResArgs); 

The DoWork function would pick this up and use it pretty much the same as the stand-alone example, except it wouldn't display anything:

C#
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker bw = sender as BackgroundWorker;
    Utility.HiResEventArgs hiResArgs = e.Argument as Utility.HiResEventArgs;
    // pick up the argument
    Int64 totalCount = 0; // loop counter total count
    Int64.TryParse((hiResArgs.Argument != null ? hiResArgs.Argument.ToString() : "100"), out totalCount);
    // if there's a lot of processing before start
    // hiResArgs.StartTime = DateTime.Now; // (optional) will re-init cycle timing
    while (hiResArgs.CycleCount < totalCount && !bw.CancellationPending)
    {
        //
        // do whatever work intended here
        Thread.Sleep(100);
        //
        hiResArgs.UpdateCycleStats(); // updates cycle stats and cycle counter
        hiResArgs.RealUserState = null; // (optional) fill this up with whatever state data you want 
        hiResArgs.RealResult = null; // (optional) fill this up with whatever result data you want 
        // send the hiResArgs to the ProgressChanged code as e.UserState
        bw.ReportProgress(hiResArgs.CyclePercentage(totalCount), // (important) sets total cycles 
        Utility.HiResEventArgs.CopyOfHiResEventArgs(hiResArgs)); // send a copy as e.UserState 
    }    
    // send the hiResArgs to the RunWorkerCompleted code as e.Result
    e.Result = Utility.HiResEventArgs.CopyOfHiResEventArgs(hiResArgs); // send a copy as e.Result
    // finally make sure to flag Cancel if necessary
    if (bw.CancellationPending)
        e.Cancel = true;
}

The ProgressChanged function takes over the display of the progress that was previously

handled directly in the stand-alone example:

C#
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    if (((BackgroundWorker)sender).CancellationPending)
        return;
    if (e.UserState != null && e.UserState.GetType().ToString().EndsWith("HiResEventArgs"))
    {
        Utility.HiResEventArgs hiResArgs = e.UserState as Utility.HiResEventArgs;
        toolStripStatusLabel1.Text = hiResArgs.CyclePercentage().ToString() + "% Done... "
                                   + " Run Time: " + hiResArgs.RunningTime
                                   + " Avg Time: " + hiResArgs.CycleAvgTime
                                   + " Cycle Time: " + hiResArgs.CycleTime;
        toolStripProgressBar1.Value = hiResArgs.CyclePercentage();
    }
    else
        toolStripProgressBar1.Value = e.ProgressPercentage;
    statusStrip1.Refresh();
}

And the final results with TotalTime would be handed in the RunWorkerCompleted Event Handler:

C#
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled == false && e.Error == null
    && e.Result != null && e.Result.GetType().ToString().EndsWith("HiResEventArgs"))
    {
        Utility.HiResEventArgs hiResArgs = e.Result as Utility.HiResEventArgs;
        // do whatever post-loop processing (display/save data, etc.)
        // hiResArgs.EndTime = DateTime.Now; // (optional) logs completion of overall task
        toolStripStatusLabel1.Text = hiResArgs.CyclePercentage().ToString() + "% Completed " 
                                   + " Run Time: " + hiResArgs.RunningTime  // the total while loop time
                                   + " Avg Time: " + hiResArgs.CycleAvgTime // average while loop interval
                                   + " Total Time: " + hiResArgs.TotalTime; // total task time 
                                                                            // (also logs completion and EndTime)
    }
    button1.Text = "Start";
}

 

Sean O'Leary

 

License

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


Written By
Software Developer (Senior) Absolute Genius
Canada Canada
Design and Develop Custom Electronics and Software to control them.
Software Engineering of custom Business Software System.
Hardware Engineering of Custom Electronics Control devices.

Comments and Discussions

 
PraiseNice work Pin
Rohit l13-Jan-18 0:40
Rohit l13-Jan-18 0:40 
QuestionDownload don't work Pin
descartes17-Aug-14 23:21
descartes17-Aug-14 23:21 
AnswerRe: Download don't work Pin
sgoleary18-Aug-14 3:22
sgoleary18-Aug-14 3:22 
GeneralRe: Download don't work Pin
descartes18-Aug-14 3:35
descartes18-Aug-14 3:35 
GeneralRe: Download don't work Pin
wallkao3bei418-Aug-14 8:20
professionalwallkao3bei418-Aug-14 8:20 
GeneralRe: Download don't work Pin
sgoleary18-Aug-14 16:25
sgoleary18-Aug-14 16:25 
GeneralRe: Download don't work Pin
wallkao3bei418-Aug-14 18:01
professionalwallkao3bei418-Aug-14 18:01 
SuggestionDon't test the textual name of a class Pin
John Brett11-Aug-14 5:38
John Brett11-Aug-14 5:38 
GeneralRe: Don't test the textual name of a class Pin
sgoleary15-Aug-14 14:19
sgoleary15-Aug-14 14:19 
GeneralRe: Don't test the textual name of a class Pin
PIEBALDconsult18-Aug-14 5:15
mvePIEBALDconsult18-Aug-14 5:15 
GeneralRe: Don't test the textual name of a class Pin
sgoleary23-Aug-14 6:56
sgoleary23-Aug-14 6:56 
Questionthis vs System.Diagnostics.Stopwatch Pin
Spirch11-Aug-14 2:21
Spirch11-Aug-14 2:21 
AnswerRe: this vs System.Diagnostics.Stopwatch Pin
sgoleary14-Aug-14 18:54
sgoleary14-Aug-14 18:54 

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.