Tek Eye Logo

Tek Eye

High Speed Regular Events in C Sharp

Occasionally an app will need to run some code at regular intervals, why? Maybe generate a data stream, plot mathematical sequences, read data from external feeds, poll a resource or external device, simulate machines or processes etc. For some financial, engineering and scientific applications the time interval required may be very small. For Windows PC .NET apps (WinForms) there is the Timer class that generates events at the millisecond level (thousandths of a second). However, what about faster events, down to microseconds (millionths of a second). Since modern CPU's are multi-core, hyper-threaded, and GHz beasts, then raw speed is not an issue. How can code be run every few microseconds?

This article provides a Tick class that can call code at intervals as low as a few microseconds, useful for running high speed regular executing code. Issues can arise when streaming events at high speed, including updating the user interface (UI) and processing all the raised events. These issues are addressed in this tutorial.

Regular microsecond events in C#

Use the Stopwatch to Measure Microseconds

The C# Stopwatch is the .NET class used for high speed timing via the Windows high-resolution performance counter, see C# High Resolution Timer. Depending upon the processor speed (see the table in the linked article for examples), the resolution can be as low as a few hundred nanoseconds. This means the Stopwatch can be used to time very small intervals. Timing down to few microseconds is well within the Stopwatch range on modern PCs. This means that code that can be executed in the order of tens of microseconds can be called at regular intervals by running the Stopwatch on a background Thread, and using its timings to trigger a regular event. This article provides example code to demonstrate that ability.

A Tick Class for High Frequency Intervals

In Visual Studio start with a new WinForms project, here called TickerDemo. A new class library project is added to the solution to hold the Stopwatch that will run in the background. The class library project is called Ticker. The class in the new Ticker project is renamed from Class1 to Tick. This separate class library allows the Tick class to be easily used in other projects.

There are several ways to run the Stopwatch on a different Thread. Here a BackgroundWorker will be used, since the events will be used to update a UI. Therefore the new class is an extension of BackgroundWorker (from System.ComponentModel):

using System.ComponentModel;

namespace Ticker
{
    public class Tick : BackgroundWorker
    {
    }
}

Next add the Stopwatch (from System.Diagnostics) and a bool that will be used internally to turn the ticking on and off. However, this bool is exposed externally to allow the ticking state to be determined, i.e. that Tick events are being generated:

using System.ComponentModel;
using System.Diagnostics;

public class Tick : BackgroundWorker
{
    Stopwatch sw = new Stopwatch();
    public bool Ticking { get; private set; }
}

That is the start of the Tick class that will be developed in the rest of the article.

Simulating a Process at High Speed

For this tutorial the Tick class is going to fire a stream of high frequency events. Each event is going to be used to roll a dice (OK die, but most people say dice). The dice rolls will be summed and the average value will be calculated. For a six sided dice the average value of all rolls should come to 3.5 (1+2+3+4+5+6=21, 21/6=3.5). This is not a difficult task and could be done easily in other ways. However, here a dice simulator is used to illustrate the generation of high frequency events. The final app will be able to run many thousands of dice rolls per second. To show how CPU speed affects the code execution speed a dice rolls per second calculation will be performed. This will allow the apps performance to be measured on different PCs with different CPUs.

The Dice Simulator UI

The TickerDemo WinForm is the UI for the dice simulator, set up the UI as follows:

  • Form1 text is set to Dice Simulator
  • Add three buttons to start, stop and reset the simulation (ButStart, ButStop, ButReset)
  • Add six labels to show a count of each number rolled on the dice (LblOneCount to LblSixCount)
  • Another six labels show which dice number applies to the count (LblOne to LblSix), set these labels to bold font
  • Finally add a label to display the calculated statistics (LblStats)

The UI should look similar to this:

Dice Simulator UI

Rolling the Dice

The C# Random class is used for the dice roll. The count of dice rolls is stored in an array of longs, these are declared at module level in Form1:

//The Dice
Random Dice = new Random();
//Store count of rolls
long[] Rolls = { 0, 0, 0, 0, 0, 0 };

When the BackgroundWorker is running, via its DoWork event, it uses the Stopwatch to count the passing microseconds. Once a set number of microseconds have passed the UI is informed via the BackgroundWorker's ReportProgress, which fires the ProgressChanged event. This normally updates the UI with progress towards completion of a background task. However, here the microseconds ticking will run until explicitly stopped. In this case ReportProgress is used to signal each timed microseconds interval instead, which is used to roll the dice. Thus, the dice roll routine needs the signature of the BackgroundWorkers ProgressChangedEventHandler. The dice roll generates a number between one and six, and updates the count array:

void ProcessTick(object sender, ProgressChangedEventArgs e)
{
    Rolls[Dice.Next(0, 6)]++;
}

Generating Microsecond Ticks

The .NET Stopwatch Frequency property returns the number of ticks pers second supported by the CPU. Divide this by 1 million to get the number of ticks per microsecond. The Tick class is given a Microseconds property that is used for the required interval:

//Number of ticks per microsecond (varies by CPU)
public readonly long TicksPerMicrosecond = Stopwatch.Frequency / 1000000L;
//Used to time the events, default 100 microseconds
long ticksToCount = 100L * Stopwatch.Frequency / 1000000L;
public long TicksToCount
{
    get { return ticksToCount; }
}
//Default to 100 microsecond events, good for most small routines
long microseconds = 100L;
//Set or get the number of microseconds for each event
public long Microseconds
{
    get { return Microseconds; }
    set
    {
        microseconds = value;
        ticksToCount = value * TicksPerMicrosecond;
    }
}

The DoWork event calls the routine that runs the Stopwatch. The routine starts the Stopwatch and monitors the elapsed ticks. Once the ticks exceed the set Microseconds the ReportProgress routine is used to raise the ProgressChange event. The UI handles this event to execute code, i.e. the dice roll. Although the ProgressChange normally reports percentages, here it just toggles an integer between 1 and 0:

//Do the microsecond timing
private void RunStopwatch(object sender, DoWorkEventArgs e)
{
    //For calculating timing difference
    long nextTrigger;
    //Store difference between ticks (waits while > 0)
    long diff;
    //Toggles from 1 to 0 and sent back via ProgressChanaged
    int tick=1;   
    //Start the stopwatch
    sw.Start();
    //Store the state
    Ticking = sw.IsRunning;
    while(Ticking)
    {
        //Get interval to wait
        nextTrigger = sw.ElapsedTicks + ticksToCount;
        do
        {
            //Don't cause a wasteful tight CPU loop
            Thread.Sleep(0);
            //See if interval has passed
            diff = nextTrigger - sw.ElapsedTicks;
        } while (diff > 0);
        //Interval passed, tell UI
        ((BackgroundWorker)sender).ReportProgress(tick);
        //Toggle reported int, indicates work
        tick = (tick==0) ? 1 : 0;
    }
    //Stop and reset the stopwatch
    sw.Stop();
    sw.Reset();
}

Starting the High Speed Events

A constructor is added to the Tick class to set up the BackgroundWorker (passing in the dice roll code to be called on the ProgressChanged event). Start and Stop methods are also added to the class:

//Use the constructor to setup the BackgroundWorker
public Tick(ProgressChangedEventHandler CallOnTick)
{
    WorkerReportsProgress = true;
    ProgressChanged += CallOnTick;
    DoWork += RunStopwatch;
}

//Start the high speed events
public void Start()
{
    if (IsBusy != true)
    {
        RunWorkerAsync();
    }
}

//Stop the high speed events
public void Stop()
{
    Ticking = false;
}

Updating the UI

Because the dice simulator performs thousands of rolls per second the UI cannot be updated on each roll. There would not be enough time to update the UI before the next roll value became available. The number of the rolls would quickly increase faster that the screen could be updated, quickly hanging the PC app. Therefore, the UI is updated every few hundredths of a second. This is fine for the user as it is in the range of normal human visual response times. To achieve this the dice roll statistics are processed on a normal WinForms timer. Drop a Timer from the toolbox onto Form1, call it TmrScreenUpdate. Set an appropriate Interval, e.g. 100 (100 milliseconds, or one tenth of a second).

The WinForm Timer Tick routine performs the calcs on the dice rolls and updates the UI:

long[] Values;  //store total value of each number
long rolls;     //number of rolls
long sum;       //sum of all rolls values
double mean;    //mean roll
//For roll rate
DateTime Current=DateTime.MinValue;
TimeSpan Diff;
private void TmrScreenUpdate_Tick(object sender, EventArgs e)
{
    //Update screen
    LblOneCount.Text = Rolls[0].ToString();
    LblTwoCount.Text = Rolls[1].ToString();
    LblThreeCount.Text = Rolls[2].ToString();
    LblFourCount.Text = Rolls[3].ToString();
    LblFiveCount.Text = Rolls[4].ToString();
    LblSixCount.Text = Rolls[5].ToString();
    //Total rolls
    rolls = Rolls.Sum();
    //For each number multiple by index for total number value rolled
    Values = Rolls.Select((rolls, index) => rolls * (index + 1)).ToArray();
    //Sum all values
    sum = Values.Sum();
    //Mean value
    mean = (double)sum / (double)rolls;
    //Calculate rolls per second
    if(Current==DateTime.MinValue && Ticker.Ticking)
        Current = DateTime.Now;
    //Update stats
    LblStats.Text = $"Total of all rolls={sum}";
    if (mean >= 0)
        LblStats.Text += $"\nMean roll={mean:N2}";
    if(rolls>0)
    {
        Diff = DateTime.Now - Current;
        LblStats.Text += $"\nRolls per second={rolls/Diff.TotalSeconds:N0}";
    }
}

Using the High Speed Event Generator

In the WinForm project add a reference to the Ticker project and declare a Tick object:

//High speed ticker for the simulation
Tick Ticker;

In the Form1 constructor initialise the Tick object, passing in the dice roll routine:

Ticker = new Tick(ProcessTick);

Add code behind the buttons to start, stop and reset the simulation:

//Start dice simulation
private void ButStart_Click(object sender, EventArgs e)
{
    Ticker.Start();
    TmrScreenUpdate.Enabled = true;
}
//Stop simulation
private void ButStop_Click(object sender, EventArgs e)
{
    TmrScreenUpdate.Enabled = false;
    Ticker.Stop();
}
//Reset dice rolls
private void ButReset_Click(object sender, EventArgs e)
{
    ButStop_Click(ButReset, e);
    for (int i = 0; i < 6; i++)
        Rolls[i] = 0;
    Current = DateTime.MinValue;
    TmrScreenUpdate_Tick(ButReset, e);
}

Adjusting the Event Generation Speed

The default setting for the Tick events is 100 microseconds, for code that runs in a few microseconds the rate of execution can be increased by setting a lower value, e.g. after Ticker object is declared:

Ticker = new Tick(ProcessTick);
Ticker.Microseconds = 20;

Depending upon the CPU the faster the ticker runs the more likely the UI thread cannot keep up. On a system with an Intel i7-4770 3.4 Ghz processor the Microseconds value could be set to 5. The simulator calculated it was running 98 thousand dice rolls per second! This compares to one thousands dice rolls per second if the simulator used a standard WinForms Timer component to perform the roll.

High Speed C# Dice Simulator

To find out the execution time of a chunk of code use the Stopwatch to measure it. This can help in determining the value for the Microseconds property for the Tick class. However, other system hardware factors may affect the performance, as the performance figures for three different computers show:

CPU Rolls per sec Microseconds setting
i5-6200U 2.3 GHz 47K 10
i5-3470 3.2 GHz 44k 8
i7-4770 3.4 Ghz 98K 5

Microsecond Timer Full Source Code

The project for this tutorial is available on GitHub. Alternatively, download the zip file from Tek Eye. The full code for the Tick.cs file developed in this article is:

using System.ComponentModel;
using System.Diagnostics;
using System.Threading;

namespace Ticker
{
    public class Tick : BackgroundWorker
    {
        //.NET hIgh speed timer
        Stopwatch sw = new Stopwatch();
        //State of the high speed events
        public bool Ticking { get; private set; }
        //Number of ticks per microsecond (varies by CPU)
        public readonly long TicksPerMicrosecond = Stopwatch.Frequency / 1000000L;
        //Used to time the events, default 100 microseconds
        long ticksToCount = 100L * Stopwatch.Frequency / 1000000L;
        public long TicksToCount
        {
            get { return ticksToCount; }
        }
        //Default to 100 microsecond events, good for most small routines
        long microseconds = 100L;
        //Set or get the number of microseconds for each event
        public long Microseconds
        {
            get { return Microseconds; }
            set
            {
                microseconds = value;
                ticksToCount = value * TicksPerMicrosecond;
            }
        }

        //Do the microsecond timing
        private void RunStopwatch(object sender, DoWorkEventArgs e)
        {
            //For calculating timing difference
            long nextTrigger;
            //Store difference between ticks (waits while > 0)
            long diff;
            //Toggles from 1 to 0 and sent back via ProgressChanaged
            int tick=1;   
            //Start the stopwatch
            sw.Start();
            //Store the state
            Ticking = sw.IsRunning;
            while(Ticking)
            {
                //Get interval to wait
                nextTrigger = sw.ElapsedTicks + ticksToCount;
                do
                {
                    //Don't cause a wasteful tight CPU loop
                    Thread.Sleep(0);
                    //See if interval has passed
                    diff = nextTrigger - sw.ElapsedTicks;
                } while (diff > 0);
                //Interval passed, tell UI
                ((BackgroundWorker)sender).ReportProgress(tick);
                //Toggle reported int, indicates work
                tick = (tick==0) ? 1 : 0;
            }
            //Stop and reset the stopwatch
            sw.Stop();
            sw.Reset();
        }

        //Use the constructor to setup the BackgroundWorker
        public Tick(ProgressChangedEventHandler CallOnTick)
        {
            WorkerReportsProgress = true;
            ProgressChanged += CallOnTick;
            DoWork += RunStopwatch;
        }
        //Start the high speed events
        public void Start()
        {
            if (IsBusy != true)
            {
                RunWorkerAsync();
            }
        }
        //Stop the high speed events
        public void Stop()
        {
            Ticking = false;
        }
    }
}

The full code for the dice simulator UI form is:

using System;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using Ticker;

namespace TickerDemo
{
    public partial class Form1 : Form
    {
        //The Dice
        Random Dice = new Random();
        //Store count of rolls
        long[] Rolls = { 0, 0, 0, 0, 0, 0 };
        //High speed ticker for the simulation
        Tick Ticker;

        public Form1()
        {
            InitializeComponent();
            Ticker = new Tick(ProcessTick);
            Ticker.Microseconds = 5;
        }

        void ProcessTick(object sender, ProgressChangedEventArgs e)
        {
            Rolls[Dice.Next(0, 6)]++;
        }

        long[] Values;  //store total value of each number
        long rolls;     //number of rolls
        long sum;       //sum of all rolls values
        double mean;    //mean roll
        //For roll rate
        DateTime Current=DateTime.MinValue;
        TimeSpan Diff;
        private void TmrScreenUpdate_Tick(object sender, EventArgs e)
        {
            //Update screen
            LblOneCount.Text = Rolls[0].ToString();
            LblTwoCount.Text = Rolls[1].ToString();
            LblThreeCount.Text = Rolls[2].ToString();
            LblFourCount.Text = Rolls[3].ToString();
            LblFiveCount.Text = Rolls[4].ToString();
            LblSixCount.Text = Rolls[5].ToString();
            //Total rolls
            rolls = Rolls.Sum();
            //For each number multiple by index for total number value rolled
            Values = Rolls.Select((rolls, index) => rolls * (index + 1)).ToArray();
            //Sum all values
            sum = Values.Sum();
            //Mean value
            mean = (double)sum / (double)rolls;
            //Calculate rolls per second
            if(Current==DateTime.MinValue && Ticker.Ticking)
                Current = DateTime.Now;
            //Update stats
            LblStats.Text = $"Total of all rolls={sum}";
            if (mean >= 0)
                LblStats.Text += $"\nMean roll={mean:N2}";
            if(rolls>0)
            {
                Diff = DateTime.Now - Current;
                LblStats.Text += $"\nRolls per second={rolls/Diff.TotalSeconds:N0}";
            }
        }

        //Start dice simulation
        private void ButStart_Click(object sender, EventArgs e)
        {
            Ticker.Start();
            TmrScreenUpdate.Enabled = true;
        }
        //Stop simulation
        private void ButStop_Click(object sender, EventArgs e)
        {
            TmrScreenUpdate.Enabled = false;
            Ticker.Stop();
        }
        //Reset dice rolls
        private void ButReset_Click(object sender, EventArgs e)
        {
            ButStop_Click(ButReset, e);
            for (int i = 0; i < 6; i++)
                Rolls[i] = 0;
            Current = DateTime.MinValue;
            TmrScreenUpdate_Tick(ButReset, e);
        }
    }
}

See Also

Comments

Hon on the 10 December 2020 at 20:28pm said: Thank You

Author:  Published:  Updated:  

ShareSubmit to TwitterSubmit to FacebookSubmit to LinkedInSubmit to redditPrint Page

Do you have a question or comment about this article?

(Alternatively, use the email address at the bottom of the web page.)

 This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

markdown CMS Small Logo Icon ↓markdown↓ CMS is fast and simple. Build websites quickly and publish easily. For beginner to expert.


Articles on:

Android Programming and Android Practice Projects, HTML, VPS, Computing, IT, Computer History, ↓markdown↓ CMS, C# Programming, Using Windows for Programming


Free Android Projects and Samples:

Android Examples, Android List Examples, Android UI Examples



Tek Eye Published Projects