DotNET Stopwatch High Precision Timing Resolutions(high-resolution timer)

C# High Resolution Timer

Timing is important for lots of the software that runs our modern world. Sometimes the timing needs to be very precise and at very small intervals. Fortunately, the Microsoft Windows Operating System (OS) has supported a high resolution timer for some time. A .NET program can access a high resolution timer via the Stopwatch class. This tutorial provides an overview of the Stopwatch class and a demo program in C#. The C# high resolution timer example provided here helps learn about precision timing for .NET programs.

Windows and Real Time

Windows was made orginally made for office based Personal Computers (PCs). Yet grew to run servers as well as the desktop and laptop computers. Windows is used to successfully run high performance data centres and to control industrial processes, even though it is not a real time operating systems (RTOS). Why can Windows be used to run time critical processes?

It all depends on how fast something needs to be controlled, and how accurate the timing needs to be. If the software on Windows is running fast enough, and the timing requirements do not need to be 100% accurate all the time, just most of the time, then Windows can be very good at handling a time sensitive task.

Windows, Hardware and Timing

Windows is not developed in isolation from the PC hardware. Microsoft works with hardware and the hardware manufacturers to bring PC improvements to market. Microsoft has a Windows Hardware Compatibility Program to ensure that Windows is properly supported by computers. Microsoft's Hardware Compatibility Specification includes the requirement for a high-precision event timer. Alongside the use of a high resolution timer, the Central Processing Unit (CPU) in computers has exceeded gigahertz (GHz) speeds since the early 2000's (1GHz being one thousand million cycles per second). Therefore, PC hardware is able to support high resolution timing.

On of the common problems with reliable timing in Windows was due to the OS needing to access slow hard disks. There would be noticeable pauses due to hard disk accesses. Now that Solid State Drives (SSDs) have supplanted mechanical drives that issue has reduced. Plus, PCs having more memory, which reduces the need to page memory to and from disk.

A high precision timer, GHz CPUs and SSDs means that Windows PCs are very fast devices. There is no excuse for a Windows computer not to be able to handle millisecond timing, most of the time.

Using the .NET Stopwatch Class

With the .NET Stopwatch class measuring time intervals is straightforward. Start by creating an instance of Stopwatch (using System.Diagnostics;):

Stopwatch sw = new Stopwatch();

There is a property to determine if a high resolution timer is present, otherwise timing resolution is in the milliseconds:

if (Stopwatch.IsHighResolution){

LstStatus.Items.Add("Using the system's high-resolution performance counter.");

}

The frequency of the stopwatch can be read:

long frequency = Stopwatch.Frequency;

LstStatus.Items.Add(string.Format("Timer frequency in ticks per second = {0}",frequency));

One modern PCs the C# Stopwatch class gives a timer resolution down to a few hundred nanoseconds (see the table later in the article).

long nanosecPerTick = (1000L * 1000L * 1000L) / frequency;

LstStatus.Items.Add(string.Format("Timer is accurate to within {0} nanoseconds",nanosecPerTick));

There are methods to start and stop the stopwatch, and to check if it is running:private void ButStart_Click(object sender, EventArgs e){

if (sw.IsRunning)

{

LstStatus.Items.Add("Stopwatch is already running");

ButStop.Enabled = true;

}

else

{

sw.Start();

LstStatus.Items.Add("Stopwatch started");

ButStop.Enabled = true;

ButStart.Enabled = false;

}

}private void ButStop_Click(object sender, EventArgs e){

if (!sw.IsRunning)

{

LstStatus.Items.Add("Stopwatch is already stopped");

ButStart.Enabled = true;}

else{

sw.Stop();

LstStatus.Items.Add("Stopwatch stopped");

ButStart.Enabled = true;

ButStop.Enabled = false;}

}

The stopwatch can be restarted and reset.private void ButRestart_Click(object sender, EventArgs e){

sw.Restart();

ButStop.Enabled = true;

ButStart.Enabled = false;

LstStatus.Items.Add("Stopwatch restarted.");

}private void ButReset_Click(object sender, EventArgs e){

sw.Reset();

LstStatus.Items.Add("Stopwatch reset.");

ButStop.Enabled = false;

ButStart.Enabled = true;

}

An finally, of course, reading the elapsed time and elaspsed milliseconds, and measuring the high resolution ticks:private void ButRead_Click(object sender, EventArgs e){

LstStatus.Items.Add(sw.Elapsed.ToString());

}private void ButReadMilli_Click(object sender, EventArgs e){

LstStatus.Items.Add(sw.ElapsedMilliseconds.ToString());

}private void ButReadTicks_Click(object sender, EventArgs e){

LstStatus.Items.Add(sw.ElapsedTicks.ToString());

}

The source code to test the .NET high resolution timer, as a C# project, is available on GitHub in the Test-Stopwatch-Class repository and here on Tek Eye in dotnet-stopwatch.zip, which also includes the project as a compiled Windows executable (StopWatchTest.exe).

Table of .NET Stopwatch High Precision Timing Resolutions

Note: Stopwatch class resolution from a variety of Windows PCs.

CPU

Ticks Per Second

Resolution in Nanoseconds

PC

i7-4770 3.4 GHz

3312657

301

Medion Desktop

i5-2500K 3.3 GHz

3215348

311

Unbranded Desktop

i5-3470 3.2 GHz

3118046

320

HP Desktop

i7-6820HQ 2.7 GHz

2648476

377

Dell Laptop

i5-M560 2.67 GHz

2597656

384

Toshiba Laptop

i5-6200U 2.3 GHz

2343747

426

Dell XPS Laptop

i5-4200U 1.6 GHz

2240909

446

Asus Laptop

See Also

Measuring Code Execution Time in C Sharp

Occasionally, part of a programmers job, once the basic functionality of an app is up and running, is to consider code performance. One task in speeding up an app is to see which parts run slower than others. Whilst .NET does support performance counters, basic timing of code execution can be done with the .NET Stopwatch class.

Using the .NET Stopwatch to Measure Code Execution Time

In this example a .NET Stopwatch is used to time the sorting of a list of numbers. The example timing code provided in this tutorial is in a basic WinForms app. After starting a new WinForms project in Visual Studio, open the starting form. Then use System.Diagnostics to access the .NET Stopwatch:

using System.Diagnostics;

Create an instance of the Stopwatch for use in the program:

Stopwatch sw = new Stopwatch();

A list of integers is going to be generated and stored in a List collection, plus another collection is going to be used to reset the orginal list. The backup list is going to be used so that the sort can be run several times. This is to illustrate a variation in .NET execution time that often happens for a first run of code:

//Number of random numbers
const int NUMBER_OF_NUMBERS = 100;
//To store some random numbers
List<int> Integers = new List<int>(NUMBER_OF_NUMBERS);
//Store a backup for resetting
List<int> Backup;

In the forms constructor generate the numbers to sort, here using the Random class:

//Generate a list of numbers
//Random number generator
Random Rng = new Random();
for (int i = 0; i < NUMBER_OF_NUMBERS; i++)
    Integers.Add(Rng.Next());
Rng = null;

Then backup the list for later list resetting:

Backup = new List<int>(Integers);

ListBox is dropped onto the form to display the numbers:

listBox1.DataSource = Integers;

Drop a Button onto the form, when clicked it will do the sort and update the display:

private void button1_Click(object sender, EventArgs e)
{
    Integers.Sort();
    //Update display
    listBox1.DataSource = null;
    listBox1.DataSource = Integers;
}

To time the sort wrap the call in a Stopwatch Start() and Stop():

sw.Start();
Integers.Sort();
sw.Stop();

The ElapsedTicks of the Stopwatch gives a count of how many processor ticks occurred. The Frequency of the Stopwatch gives the number of ticks per second. Thus, the time is ElapsedTicks/Frequency, converted to double to avoid division issues:

double elapsedTime = (double)sw.ElapsedTicks / (double)Stopwatch.Frequency;

This will be a small number with our modern processors, therefore, convert it to microseconds by multiplying by 1,000,000 (or milliseconds by times 1,000), makes for easy reading:

string executionTime = (elapsedTime * 1000000).ToString("F2") + " microseconds";

Drop a ListBox onto the form to display the timings:

listBox2.Items.Add(executionTime);

Reset the Stopwatch ready for the next thing to time:

sw.Reset();

To run the sort several times, add another button to restore the original list to its initial starting condition:

private void button2_Click(object sender, EventArgs e)
{
    //Reset to original random list
    Integers = new List<int>(Backup);
    listBox1.DataSource = Integers;
}    

Here is the code's first run showing the unsorted numbers:

Here is the timing of the sort execution. The code was run on a PC with an i7-4770 3.4 GHz Intel CPU that produces 3,312,657 ticks per second (the number of ticks will vary by CPU). The timings list shows several runs of the sort (using the sort and reset buttons several times):

Notice that there is a variation in the time taken to execute the sort function, especially the first sort. Variation in code execution times is down to several factors. Those factors can include one or many of the following (though not all of the following applies in the simple example shown here):

  • The data being processed
  • Windows and .NET memory caching
  • .NET JITing
  • Garbage collection
  • The Windows operating systems (OS) running other tasks (Windows is not a real-time operating system)
  • Disk caching
  • CPU caching
  • Accessing system resources (disks, network, Internet, databases, etc.)

When looking to speed up code use the timings after the first pass has executed. If first run time is important then Ngen can be used to reduce the start time.

Full Timing Execution Example Code

Here is the full code for the form used to demonstrate the timing of executing code:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Diagnostics;

 

namespace ExecutionTiming
{
    public partial class Form1 : Form
    {
        Stopwatch sw = new Stopwatch();
        //Number of random numbers
        const int NUMBER_OF_NUMBERS = 100;
        //To store some random numbers
        List<int> Integers = new List<int>(NUMBER_OF_NUMBERS);
        //Store a backup for resetting
        List<int> Backup;

 

        public Form1()
        {
            InitializeComponent();
            //Generate a list of numbers
            //Random number generator
            Random Rng = new Random();
            for (int i = 0; i < NUMBER_OF_NUMBERS; i++)
                Integers.Add(Rng.Next());
            Rng = null;
            //Back up the unsorted list for reseting later
            Backup = new List<int>(Integers);
            //Display the list to sort
            listBox1.DataSource = Integers;
        }
        //Sort the list and time the sort
        private void button1_Click(object sender, EventArgs e)
        {
            sw.Start();
            Integers.Sort();
            sw.Stop();
            //Calculate elapsed time
            double elapsedTime = (double)sw.ElapsedTicks / (double)Stopwatch.Frequency;
            //Convert to a string
            string executionTime = (elapsedTime * 1000000).ToString("F2") + " microseconds";
            //Show the execution time
            listBox2.Items.Add(executionTime);
            //Reset the stopwatch ready for next timing
            sw.Reset();
            //Update sort display
            listBox1.DataSource = null;
            listBox1.DataSource = Integers;
        }
        //Reset to the original unsorted list
        private void button2_Click(object sender, EventArgs e)
        {
            //Reset to original random list
            Integers = new List<int>(Backup);
            //Show the unsorted list again
            listBox1.DataSource = Integers;
        }
    }
}

See Also

C# BackgroundWorker with Progress Bar

When developing software not only is the program's main purpose important, i.e. what task the program performs, there are other aspects to consider. These other aspects of a computer program include things like usability, security and maintainability. This article looks at an important part of usability, specifically how the interface for a C# program can keep the user updated and the User Interface (UI) responsive when running intensive tasks. In this tutorial this is done with the BackgroundWorker and ProgressBar .NET Framework classes.

The Importance of Program Responsiveness

In terms of usability the program's UI should be informative, responsive and reflect the status of the program and the data it is processing. A program should keep the user updated on what it is doing. One of the annoying problems of badly written software is when it fails to provide useful feedback and fails to respond to users actions. In such cases the user may force the program to close because it gives the impression it has stopped working.

Modern computers are extremely powerful and often come with multiple processing cores that can run many threads. It is easy for a modern PC to support multithreaded programs. Therefore, there is no excuse for software to not be responsive and user friendly. However, even popular operating systems and programs still fail to provide a good user experience. Part of the problem lies with the software writers. Whilst it is easy for a PC to run multithreaded programs, some developers are not inclined to develop them because they can be harder to debug if programming errors are made. However, for those developing Windows applications on the .NET Framework there are classes available that an application can use to run multiple threads.

BackgroundWorker Class Runs Code on Another Thread

A normal Windows Forms (WinForms) application has one thread. The main UI thread, which not only runs the main program code it also services all the screen interface elements. If intensive code, such as complex calculations, or slow code, such as heavy Internet access, runs in the main program code, then the UI can become unresponsive.

The BackgroundWorker is available from the Visual Studio Toolbox (although BackgroundWorker can be used programmatically). BackgroundWorker is straightforward to use, there are a couple of examples at the Microsoft BackgroundWorker documentation. However, it is often misused, with a few programmers thinking it doesn't work. Usually because they don't really understand its correct operation.

Using the BackgroundWorker

Any intensive or slow code can be run by the BackgroundWorker off the DoWork event. However, the slow or intensive code must be self-contained and cannot access UI elements or other methods on the UI thread. To support this self-containment and communicate with the UI thread (to update the interface) the ProgressChanged event can be used. Finally, once the task called by the DoWork event has completed, the BackgroundWorker can fire another event, RunWorkerCompleted, to let the main program know the task has finished. Finally, if necessary, the background task can be cancelled using BackgroundWorker's CancelAsync method.

In this example the BackgroundWorker is going to count the English letter characters in a text file. This task has been chosen to illustrate the workings of a BackgroundWorker, a simple example of processing that can be moved to a BackgroundWorker. Though unless it is processing a hundred megabytes plus file, it still runs very fast on a modern computer.

(Why count the English letters in a file? Counting letter frequency is a technique used in forensic and cryptography applications. Here it is just used as a file processing example.)

BackgroundWorker with ProgressBar Example Code

As shown in the image at the start, the simple app allows a text file to be chosen (using a FileOpenDialog), and then analysed, with percentage progress shown, and messages added to a list (ListBox). A BackgroundWorker is available from the Toolbox and can be dropped onto a WinForm. Note, the code for the actual WinForm construction is not shown as it standard stuff. Download the Visual Studio solution in the backgroundworker-demo.zip file to have a look at all the source. Here is the main code:

using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.IO;

 

namespace WorkerTest
{
    public partial class WorkerTest : Form
    {
        //Count the frequency of different characters
        long LowercaseEnglishLetter = 0;
        long LowercaseNonEnglishLetter = 0;
        long UppercaseEnglishLetter = 0;
        long UppercaseNonEnglishLetter = 0;
        long EnglishLetter = 0;
        long Digit0to9 = 0;
        long Whitespace = 0;
        long ControlCharacter = 0;
        //Count the frequency of English letters
        long[] LetterFrequency=new long[26];

 

        public WorkerTest()
        {
            InitializeComponent();
        }
        #region Buttons and Listbox
        //Get the file
        private void ButChooseFile_Click(object sender, EventArgs e)
        {
            DialogResult result = FileDialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                TxtFile.Text = FileDialog.FileName;
                ButGo.Enabled = true;
            }
        }
        //Process the file
        private void ButGo_Click(object sender, EventArgs e)
        {
            ButCancel.Enabled = true;
            //Clear previous results
            ButClear_Click(this, null);
            if (!File.Exists(TxtFile.Text))
            {
                AddMessage("The file " + TxtFile.Text + " does not exist.");
            }
            else
            {
                AddMessage("Starting letter analysis...");
                if (BgrdWorker.IsBusy != true)
                {
                    ButGo.Enabled = false;
                    // Start the asynchronous operation.
                    BgrdWorker.RunWorkerAsync(TxtFile.Text);
                }
            }
        }
        //Cancel the processing
        private void ButCancel_Click(object sender, EventArgs e)
        {
            if (BgrdWorker.WorkerSupportsCancellation == true)
            {
                // Cancel the asynchronous operation.
                BgrdWorker.CancelAsync();
            }
        }
        //Clear the data
        private void ButClear_Click(object sender, EventArgs e)
        {
            LstStatus.Items.Clear();
            Progress.Value = 0;
            LetterFrequency = new long[26];
            LowercaseEnglishLetter = 0;
            LowercaseNonEnglishLetter = 0;
            UppercaseEnglishLetter = 0;
            UppercaseNonEnglishLetter = 0;
            EnglishLetter = 0;
            Digit0to9 = 0;
            Whitespace = 0;
            ControlCharacter = 0;
        }
        //User feedback in listbox
        int AddMessage(string MessageToAdd)
        {
            //Limit number of items
            if (LstStatus.Items.Count >= 60000)
                LstStatus.Items.RemoveAt(0);
            int ret = LstStatus.Items.Add(MessageToAdd);
            //ensure new item is visible
            LstStatus.TopIndex = LstStatus.Items.Count - 1;
            return ret;
        }
        #endregion
        #region  BackgroundWorker
        private void BgrdWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            string file;            //name of file to analyse
            long fileLength;        //store total number bytes to process
            long bytesProcessed;    //Count the characters processed
            int nextChar;           //stores each char to analyse
            int progress;           //percentage for progress reporting
            BackgroundWorker worker = sender as BackgroundWorker;   //who called us

 

            try
            {
                //get the file to process
                file = (string)e.Argument;
                //How many bytes to process?
                fileLength = (new FileInfo(TxtFile.Text)).Length;
                bytesProcessed = 0; //none so far
                // Create an instance of StreamReader to read from file
                // The using statement also closes the StreamReader
                using (StreamReader sr = new StreamReader(file))
                {
                    //until end of the file
                    while((nextChar = sr.Read()) != -1)
                    {
                        //has the operation been cancelled
                        if (worker.CancellationPending == true)
                        {
                            e.Cancel = true;
                            break;
                        }
                        else
                        {
                            //Now process the character
                            AnalyseChar((char)nextChar);
                            bytesProcessed += 1;
                            //Report back every 100000 chars
                            if (bytesProcessed % 100000 == 0)
                            {
                                //report progress
                                //actual percentage calculated on number of processed bytes
                                progress=(int)Math.Ceiling(((float)bytesProcessed / fileLength) * 100);
                                worker.ReportProgress(progress, bytesProcessed);
                            }
                        }
                    }
                    e.Result = bytesProcessed;
                }
            }
            catch (Exception ex)
            {
                throw new Exception ("Error analysing text file: " + ex.ToString());
            }
        }
        //Inform user of pregress
        private void BgrdWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            AddMessage("Processed: " + ((long)e.UserState).ToString() + " bytes");
            Progress.Value = e.ProgressPercentage;
            LblPercent.Text = Progress.Value.ToString() + "%";
        }
        //Finished the processing
        private void BgrdWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            ButCancel.Enabled = false;
            ButGo.Enabled = true;
            if (e.Cancelled == true)
            {
                AddMessage("Analysis aborted.");
            }
            else if (e.Error != null)
            {
                AddMessage("Analysis error: " + e.Error.Message);
            }
            else
            {
                //100% completed
                Progress.Value = 100;
                LblPercent.Text = "100%";
                //Print results
                AddMessage("Analysis completed, bytes processed: " + ((long)e.Result).ToString());
                if (LowercaseEnglishLetter > 0)
                    AddMessage("Total Lowercase English Letters=" + LowercaseEnglishLetter.ToString());
                if (UppercaseEnglishLetter > 0)
                    AddMessage("Total Uppercase English Letters=" + UppercaseEnglishLetter.ToString());
                if (LowercaseNonEnglishLetter > 0)
                    AddMessage("Total Lowercase Non-English Letters=" + LowercaseNonEnglishLetter.ToString());
                if (UppercaseNonEnglishLetter > 0)
                    AddMessage("Total Uppercase Non-english Letters=" + UppercaseNonEnglishLetter.ToString());
                if (Digit0to9 > 0)
                    AddMessage("Total Digits=" + Digit0to9.ToString());
                if (Whitespace > 0)
                    AddMessage("Total Whitespace=" + Whitespace.ToString());
                if (ControlCharacter > 0)
                    AddMessage("Total Control Characters=" + ControlCharacter.ToString());
                AddMessage("");
                //Show frequency of english letters
                if (EnglishLetter > 0)
                {
                    AddMessage("Total number of English letters:" + EnglishLetter.ToString());
                    double LetterPercentage;
                    string PrintResult;
                    for (int i = 0; i < 26; i++)
                    {
                        LetterPercentage = ((double)LetterFrequency[i] / EnglishLetter) * 100.0;
                        PrintResult = ((char)(i + 65)).ToString();
                        for (int j = 0; j < Math.Round(LetterPercentage); j++)
                            PrintResult += "-";
                        AddMessage(PrintResult + " " + LetterPercentage.ToString("n3") + "%");
                    }
                }
            }
        }
        //Analyse a single character
        void AnalyseChar(char Character)
        {
            if (char.IsLower(Character))
            {
                if (Character >= 'a' && Character <= 'z')
                {
                    LowercaseEnglishLetter++;
                    LetterFrequency[Character - 'a']++;
                }
                else
                {
                    LowercaseNonEnglishLetter++;
                }
            }
            else if (char.IsUpper(Character))
            {
                if (Character >= 'A' && Character <= 'Z')
                {
                    UppercaseEnglishLetter++;
                    LetterFrequency[Character - 'A']++;
                }
                else
                {
                    UppercaseNonEnglishLetter++;
                }
            }
            else if (char.IsDigit(Character))
            {
                Digit0to9++;
            }
            else if (char.IsWhiteSpace(Character))
            {
                Whitespace++;
            }
            else if (char.IsControl(Character))
            {
                ControlCharacter++;
            }
            EnglishLetter = LowercaseEnglishLetter + UppercaseEnglishLetter;
        }
        #endregion
    }
}

Running the BackgrounderWorker Example

Unless you have a tens of megabytes plus text file, the processing will be very quick. (If you want some big text files an Internet search will reveal several sources). Due to the fast processing the ProgressBar update lags behind the file processing, the UI is slow to update (to improve that aspect the number of characters processed before calling ReportProgress can be increased). To see the processing and ProgressBar update in action on smaller files slow down the processing by getting the background thread to wait a little longer:

//Report back every 100000 chars
if (bytesProcessed % 100000 == 0)
{
    //Add this line to slow the processing
    System.Threading.Thread.Sleep(100);
 
    //report progress
    .
    .
    .

Once the code for this BackgroundWorker example has been examined it will be seen that it is a useful template for other intensive or slow processing tasks that a C# program will need to do. Get code in the backgroundworker-demo.zip file or from GitHub.

using System.Diagnostics;

/// <summary>

///  This class is meant to operate as a more precise alternativer to System.Threading.Timer. It creates a new thread

///  and runs a callback on set intervals. The thread does not sleep, but is blocked instead to acheive greater precision.

///  Set active to false to stop the callback function

///  Create a new instance of the class to run the callback

///  e.g. Worker tmp = new Worker(aFunction, -1, 20, 0, true);

/// </summary>

public class Worker {

    Stopwatch processTimer;

    public bool active;

    public object connectionID;

    public double intervalMs;

    public int offsetMs;

    bool carryOverEnabled;

    double carryover;

 

    /// <summary>

    ///  Class constructor. Starts thread to run callback

    /// </summary>

    /// <param name="callback">The function to be called on the specified interval. Currently set to be an integer returning function</param>

    /// <param name="_connectionID">used for networking. -1 can be used if this is used for non-networking purposes</param>

    /// <param name="_intervalMs">Callback will be called every _intervalMs milliseconds</param>

    /// <param name="_initialOffset">The number of Milliseconds to wait unil the first call of the callback method. set to 0 to start immediately</param>

    /// <param name="_carryOverEnabled">

    /// When true, the Worker will try to "catch up" if it fails to call callback before _intervalMs ends. This will make subsequent intervals

    /// shorter until it is able to catch up

    /// </param>

    public Worker(Func<int> callback, object _connectionID, double _intervalMs, double _initialOffset, bool _carryOverEnabled) {

        processTimer = Stopwatch.StartNew();

        active = true;

        connectionID = _connectionID;

        intervalMs = _intervalMs;

        offsetMs = _initialOffset;

        carryOverEnabled = _carryOverEnabled;

 

        Thread workerThread = new Thread(DoWork);

        workerThread.Start();

    }

 

    public ~Worker() {

        processTimer.Stop();

    }

 

    /// <summary>

    ///  This is the infinite wait, function call repeat loop for the callback. Will stop if active is set to false

    /// </summary>

    private void DoWork() {

        blockThread(offsetMs);

        while (true) {

            if (!active)

                break;

 

            callback();

 

            blockThread(intervalMs);

        }

    }

 

    /// <summary>

    ///  thread blocking method. uses the stopwatch to determine when to stop blocking

    /// </summary>

    private void blockThread(double durationInMs) {

        Stopwatch sw = Stopwatch.StartNew();

        double processingTime = processTimer.Elapsed.TotalMilliseconds;

        double TargetPause = durationInMs;

        if (carryOverEnabled) { //allows catch up

            double realDurationMs = TargetPause - (processingTime + carryover);

            carryover = 0.0;

            if (realDurationMs < 0.0) { //if there isn't enough pause time left after processing time, carry it to next pause. This will allow wait-to-start

                carryover = -1 * realDurationMs;

            }

        }else { // no catch up

            double realDurationMs = TargetPause - processingTime;

        }

        while (sw.Elapsed.TotalMilliseconds < realDurationMs) {

        }

        processTimer = Stopwatch.StartNew();

        sw.Stop();

    }

}


    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using System.Threading;
    
    
    namespace HighResolutionTimer
    {
        /// <summary>
        /// Hight precision non overlapping timer
        /// Came from 
        /// https://stackoverflow.com/a/41697139/548894
        /// </summary>
        /// <remarks>
        /// This implementation guaranteed that Elapsed events 
        /// are not overlapped with different threads. 
        /// Which is important, because a state of the event handler attached to  Elapsed,
        /// may be left unprotected of multi threaded access
        /// </remarks>
        public class HighResolutionTimer
        {
            /// <summary>
            /// Tick time length in [ms]
            /// </summary>
            public static readonly double TickLength = 1000f / Stopwatch.Frequency;
    
            /// <summary>
            /// Tick frequency
            /// </summary>
            public static readonly double Frequency = Stopwatch.Frequency;
    
            /// <summary>
            /// True if the system/operating system supports HighResolution timer
            /// </summary>
            public static bool IsHighResolution = Stopwatch.IsHighResolution;
    
            /// <summary>
            /// Invoked when the timer is elapsed
            /// </summary>
            public event EventHandler<HighResolutionTimerElapsedEventArgs> Elapsed;
    
            /// <summary>
            /// The interval of timer ticks [ms]
            /// </summary>
            private volatile float _interval;
    
            /// <summary>
            /// The timer is running
            /// </summary>
            private volatile bool _isRunning;
    
            /// <summary>
            ///  Execution thread
            /// </summary>
            private Thread _thread;
    
            /// <summary>
            /// Creates a timer with 1 [ms] interval
            /// </summary>
            public HighResolutionTimer() : this(1f)
            {
            }
    
            /// <summary>
            /// Creates timer with interval in [ms]
            /// </summary>
            /// <param name="interval">Interval time in [ms]</param>
            public HighResolutionTimer(float interval)
            {
                Interval = interval;
            }
    
            /// <summary>
            /// The interval of a timer in [ms]
            /// </summary>
            public float Interval
            {
                get => _interval;
                set
                {
                    if (value < 0f || Single.IsNaN(value))
                    {
                        throw new ArgumentOutOfRangeException(nameof(value));
                    }
                    _interval = value;
                }
            }
    
            /// <summary>
            /// True when timer is running
            /// </summary>
            public bool IsRunning => _isRunning;
    
            /// <summary>
            /// If true, sets the execution thread to ThreadPriority.Highest
            /// (works after the next Start())
            /// </summary>
            /// <remarks>
            /// It might help in some cases and get things worse in others. 
            /// It suggested that you do some studies if you apply
            /// </remarks>
            public bool UseHighPriorityThread { get; set; } = false;
    
            /// <summary>
            /// Starts the timer
            /// </summary>
            public void Start()
            {
                if (_isRunning) return;
    
                _isRunning = true;
                _thread = new Thread(ExecuteTimer)
                {
                    IsBackground = true,
                };
    
                if (UseHighPriorityThread)
                {
                    _thread.Priority = ThreadPriority.Highest;
                }
                _thread.Start();
            }
    
            /// <summary>
            /// Stops the timer
            /// </summary>
            /// <remarks>
            /// This function is waiting an executing thread (which do  to stop and join.
            /// </remarks>
            public void Stop(bool joinThread = true)
            {
                _isRunning = false;
    
                // Even if _thread.Join may take time it is guaranteed that 
                // Elapsed event is never called overlapped with different threads
                if (joinThread && Thread.CurrentThread != _thread)
                {
                    _thread.Join();
                }
            }
    
            private void ExecuteTimer()
            {
                float nextTrigger = 0f;
    
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
    
                while (_isRunning)
                {
                    nextTrigger += _interval;
                    double elapsed;
    
                    while (true)
                    {
                        elapsed = ElapsedHiRes(stopwatch);
                        double diff = nextTrigger - elapsed;
                        if (diff <= 0f)
                            break;
    
                        if (diff < 1f)
                            Thread.SpinWait(10);
                        else if (diff < 5f)
                            Thread.SpinWait(100);
                        else if (diff < 15f)
                            Thread.Sleep(1);
                        else
                            Thread.Sleep(10);
    
                        if (!_isRunning)
                            return;
                    }
    
    
                    double delay = elapsed - nextTrigger;
                    Elapsed?.Invoke(this, new HighResolutionTimerElapsedEventArgs(delay));
    
                    if (!_isRunning)
                        return;
    
                    // restarting the timer in every hour to prevent precision problems
                    if (stopwatch.Elapsed.TotalHours >= 1d)
                    {
                        stopwatch.Restart();
                        nextTrigger = 0f;
                    }
                }
    
                stopwatch.Stop();
            }
    
            private static double ElapsedHiRes(Stopwatch stopwatch)
            {
                return stopwatch.ElapsedTicks * TickLength;
            }
        }
    
    
        public class HighResolutionTimerElapsedEventArgs : EventArgs
        {
            /// <summary>/// Real timer delay in [ms]/// </summary>
            public double Delay { get; }
    
            internal HighResolutionTimerElapsedEventArgs(double delay)
            {
                Delay = delay;
            }
        }
    }
   

    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using System.Threading;
    
    
    namespace HighResolutionTimer
    {
    
        class Program
        {
            private static volatile bool _isFinished;
            
            static void Main(string[] args)
            {
                Console.WriteLine($"IsHighResolution = {HighResolutionTimer.IsHighResolution}");
                Console.WriteLine($"Tick time length = {HighResolutionTimer.TickLength} [ms]");
    
                var stopwatch = new Stopwatch();
                
                int count = 0;
                var values = new double[10000];   // The number of repetitions
    
                HighResolutionTimer timer = new HighResolutionTimer(0.5f);
                timer.Elapsed += (s, e) =>
                {
                    values[count] = stopwatch.Elapsed.TotalMilliseconds;
                    stopwatch.Restart();
                    
                    if (++count == values.Length)
                    {
                        timer.Stop();
                        _isFinished = true;
                    }
                };
                    
                stopwatch.Start();
                timer.Start();
                while (!_isFinished) Thread.Sleep(1); // Just wait for repetitionCount 
    
                var tsv = new StringBuilder();
                foreach (var value in values)
                {
                    Console.WriteLine(value);
                    tsv.AppendLine(value.ToString(CultureInfo.InvariantCulture));
                }
    
                File.WriteAllText("profile.txt", tsv.ToString());
            }
        }
    }


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章