Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Background Cycle Timing

0.00/5 (No votes)
20 Aug 2014 1  
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:

// 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:

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.

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:

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:

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:

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here