Tek Eye Logo

Tek Eye

C# Async Await Microseconds Timer with Callback

In the Tek Eye article C# High Resolution Timer a test app for the Stopwatch class was provided. The Stopwatch provides access to the underlying Windows QueryPerformanceCounter, or QPC. In this tutorial the Stopwatch is used to provide a timer implemented using async and await. To see a version that is implemented using a BackgroundWorker see the the Tek Eye article High Speed Regular Events in C Sharp. The timer provided here uses the C# async, await, and a callback, via a C# delegate. It enables very high frequency regular execution of code, down to microsecond (μs) resolution.

Stopwatch

In this article the timer is a high performance in terms of event frequency. The timer is far quicker than the standard .Net Timer class which provides timing in milliseconds (ms), or thousandths of a second. The timer class in this tutorial allows for events in the μs range. A microsecond is a millionth of a second. This is an improvement by a factor of a thousand over the standard Timer class. The trade-off is that the microsecond timer will use a CPU thread, however, on modern multi-core, hyper-threaded machines, that should not be an issue.

This tutorial assumes you can edit and run C# code on your development computer. Visual Studio is the Microsoft preferred environment for developing C# code, and is used in this tutorial. A complete coded listing is provided at the end of the article. You can download the Visual Studio solution and projects in microtimer.zip. (Once you unzip the solution and project files you may have to use Windows Explorer to view the files properties and unblock the files. This is a recent Windows security feature. Only download and use files from reputable sources.)

Start a C# Microsecond Timer WinForms Project

Create a new WinForms project in Visual Studio, here called MicroTimerTest. The Form in the MicroTimerTest project is named FrmMicroTimerTest and will be used to test a MicroTimer C# class. Create a project called FastTimer for the MicroTimer class. The project's namespace will be FastTimer, though the class will be named MicroTimer and stored in MicroTimer.cs. Programs reference FastTimer (using FastTimer;) and then use the MicroTimer class to run code at intervals down to microseconds. The MicroTimerTest project is going to simulate hundreds of thousands of dice rolls per second.

Note: Die is the singular of dice, but people say dice for one die, and the same is used in this article.

The code for the dice roll is contained in its own class in a file called Dice.cs, and is based on the dice code from the article C# Dice Roll Code with Stats.

3D Dice for the Dice Roller Source Code

Add a Dice C# class to the MicroTimerTest project, stored in Dice.cs. The Visual Studio solution should then look similar to this:

The MicroTimer project

Add the reference to the FastTimer project in the MicroTimerTest project, then add using FastTimer; to the top of FrmMicroTimerTest. Declare a MicroTimer object within FrmMicroTimerTest:

MicroTimer msTimer = new MicroTimer();

Add the Dice Code

The code for the Dice class is similar to the dice code in the article linked above. Here implemented as a static class as only one dice is being used. The dice roller simply uses the C# Random class to generate the dice roll. The rest of the code provides some dice rolling statistics:

using System;
using System.Linq;

namespace MicroTimerTest
{
    public static class Dice
    {
        //Store number of rolls of each number
        static long[] rolls = { 0, 0, 0, 0, 0, 0 };
        //Use random for rolling
        static readonly Random roller = new Random();
        //Store the roll;
        static int lastRoll = 0;
        //Perform a dice roll
        public static void Roll()
        {
            //Roll a number 0 to 5
            lastRoll = roller.Next(0, 6);
            //Increment rolled count
            //and correct 0 to 5 -> 1 to 6
            ++rolls[lastRoll++];
        }
        //Get last rolled number
        public static int LastRoll => lastRoll;
        //Totals of each number rolled
        public static long[] Rolls => rolls;
        //Total of all rolls performed
        public static long TotalRolls => rolls.Sum();
        //Total value of all rolled numbers
        public static long[] TotalsOfRolledNumbers => rolls.Select((number, index) => number * (index + 1)).ToArray();
        //Total of all values rolled
        public static long TotalValues => TotalsOfRolledNumbers.Sum();
        //Mean number rolled (double cast required to prevent integer only division) 
        public static double MeanValueRolled => TotalValues / (double)TotalRolls;
    }
}

It is the Roll method that will be called by the μs resolution timer, using Dice.Roll(). Start by testing that the dice class is working, build a user interface (UI) to test the Roll method and display the statistics.

Start Building the UI for the Test Program

Drop a Button onto the WinForm, used to test a single roll of the dice, and a Label to store the result of the dice roll. Then 18 labels are needed to display the dice statistics and their captions:

Base UI

The Button code to roll the dice and update the labels will be similar to this:

private void button1_Click(object sender, EventArgs e)
{
    Dice.Roll();
    UpdateUI(null, 0);
}

void UpdateUI(object sender, int counter)
{   
    label1.Text = Dice.LastRoll.ToString();
    label3.Text = Dice.Rolls[0].ToString();
    label5.Text = Dice.Rolls[1].ToString();
    label7.Text = Dice.Rolls[2].ToString();
    label9.Text = Dice.Rolls[3].ToString();
    label11.Text = Dice.Rolls[4].ToString();
    label13.Text = Dice.Rolls[5].ToString();
    label15.Text = Dice.TotalRolls.ToString();
    label17.Text = Dice.TotalValues.ToString();
    label19.Text = Dice.MeanValueRolled.ToString();
}

The function to update the labels has extra parameters which will be used later for a progress report by the MicroTimer.

Building and Testing the MicroTimer Class

With dice code working, build the MicroTimer class. The C# Stopwatch uses the high performance timer in Windows and provides the required resolution for the MicroTimer. Add a Stopwatch to the MicroTimer class:

Stopwatch sw = new Stopwatch();

When the MicroTimer fires it will call an external function assigned to a delegate, therefore, add a delegate to the class:

//The delegate used to call an external function
public delegate void ExternalCode();
public ExternalCode OnTimeout;

In FrmMicroTimerTest the Dice.Roll() method is assigned to the delegate after InitializeComponent():

msTimer.OnTimeout += Dice.Roll;

A Microseconds property will be used to store the number of microseconds that the timer must count before calling the assigned function (the delegate). The default value is 100μs, which provides a timeout of 0.1 milliseconds, ten times faster than the fastest C# standard Timer setting. The Microseconds value can be set via the class constructor. Within the constructor the microseconds value is converted into the number of CPU ticks, from Stopwatch.Frequency. The CPU ticks are counted to then run the timeout delegate:

//Store the timeout period in microseconds
private int microSeconds;
//and the equivalent CPU ticks
private long microSecondsInCPUTicks;
//Accessors for the microseconds property
//and conversion to the CPU ticks equivalent
public int MicroSeconds
{
    get
    {
        return microSeconds;
    }
    set
    {
        microSeconds = value;
        microSecondsInCPUTicks = value * Stopwatch.Frequency / 1000000L;
    }
}
//Constructors
public MicroTimer(int microSeconds = 100) => MicroSeconds = microSeconds;

Separating Regular UI Updates from the Fast Running Code

This timer is likely to be used for code that needs to be run at high frequency. Therefore, UI updates are unlikley to be possible within the time critical code. To support an additional update method for user feedback, the MicroTimer will support the use of the Progress object. The Progress class supports the Report method that can be used to send feedback to the UI thread:

//Progress reporter
public IProgress<int> Updater { get; set; }

Milliseconds are used for the feedback rate:

//Default feedback rate to a 10th of a second
private int feedbackMilliseconds = 100;
//in the equivalent CPU ticks
private long feedbackSecondsInCPUTicks = Stopwatch.Frequency / 100;
public int FeedbackMilliseconds
{
    get
    {
        return feedbackMilliseconds;
    }
    set
    {
        feedbackMilliseconds = value;
        feedbackSecondsInCPUTicks = value * Stopwatch.Frequency / 1000;
    }
}

The MicroSeconds, Updater, and FeedbackMilliseconds properties, and the internal variables they set, will be used in the microsecond timing function.

Setting the Microseconds and FeedbackMilliseconds in the UI

Test the Microseconds and FeedbackMilliseconds properties in the UI. Drop two NumericUpDowncontrols onto the test form, they support typing in numeric values and will be used to read and set the Microseconds and FeedbackMilliseconds properties. Set the Minimum values for the NumericUpDown controls to 1, and the Maximum values as required, e.g. 10000000 (10,000,000μs, i.e. 10 seconds) for the Microseconds, and 1000 (1 second) for the FeedbackMilliseconds:

public FrmMicroTimerTest()
{
    InitializeComponent();
    msTimer.OnTimeout += Dice.Roll;
    numericUpDown1.Value = msTimer.MicroSeconds;
    numericUpDown2.Value = msTimer.FeedbackMilliseconds;
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
    msTimer.MicroSeconds = (int)numericUpDown1.Value;
}
private void numericUpDown2_ValueChanged(object sender, EventArgs e)
{
    msTimer.FeedbackMilliseconds = (int)numericUpDown2.Value;
}

Supporting Cancellation of the Timer Events

The microseconds timer will run on a different thread from the UI. This is achieved using C# async and await. Stopping the timer from the UI is via a CancellationToken, which is provided to the timing thread via CancellationTokenSource when the await is executed:

//Support cancelling to stop the timer
CancellationTokenSource stopTimer;

A new CancellationTokenSource will be declared when the timing function is started. To send a cancel request to the timing function the Cancel() method is used:

//Stop the timer
public void Stop()
{
    if(stopTimer != null)
        stopTimer.Cancel();
}

The Core MicroTimer Function

The core Timing function in the MicroTimer takes the Progress object and a CancellationToken. The function then starts the Stopwatch, this runs until stopped by the CancellationToken. While the Stopwatch runs the set microseconds interval, based on the Stopwatch frequency, is calculated. That interval is used to check the number of ElapsedTicks by the Stopwatch, and loops until the interval has passed. The external code attached to the OnTimeout delegate can then be called. A progressCounter is incremented on each interval and once the set progress period has been reached the Report function is called to inform the original (usually UI) thread:

//Run the timer
void Timing(IProgress<int> progress, CancellationToken cancelTimimg)
{
    //Calculates the timing difference
    long interval;
    //Counter for progress timing
    int progressCounter = 0;
    //Start the stopwatch
    sw.Start();
    //Loop until stopped
    while (sw.IsRunning)
    {
        //Get interval to wait
        interval = sw.ElapsedTicks + microSecondsInCPUTicks;
        //Loop until interval has passed
        while (interval - sw.ElapsedTicks > 0)
        {
            //Chance for kernel to yield the tight CPU loop
            Thread.Sleep(0);
        }
        //Run the external code
        OnTimeout();
        //see if a progress report is required
        if (++progressCounter >= feedbackSecondsInCPUTicks / microSecondsInCPUTicks)
        {
            progress.Report(progressCounter);
            progressCounter = 0;
        }
        if (cancelTimimg.IsCancellationRequested)
            sw.Stop();
    }
}

The IsCancellationRequested property of the CancellationToken is used to stop the MicroTimer (see the Stop method in the previous section), and this causes the timing function to exit.

Using async await to Start the MicroTimer

If the MicroTimer has not been started, and the delegate has been set, then a new CancellationTokenSource is created and passed with the set Progress object to the Timing function. The Timing function is called using await and Task.Run(). The use of Task.Run() is because the Timing function is a CPU bound operation. When Timing stops the CancellationTokenSource can be disposed off and set to null ready for the next start request. The Start method is declared with async because of the use of await:

//Start the MicroTimer
public async void Start()
{
    if (stopTimer == null && OnTimeout != null)
    {
        stopTimer = new CancellationTokenSource();
        await Task.Run(() => Timing(Updater, stopTimer.Token));
        stopTimer.Dispose();
        stopTimer = null;
    }
}

A button added to the form (with the Text set to Start initially) toggles between starting and stopping the MicroTimer. When the MicroTimer is started a new Progress object is created and assigned to the MicroTimer. The ProgressChanged event is set to the function that updates the forms labels with the dice statistics:

Progress<int> update;
private void button2_Click(object sender, EventArgs e)
{
    //Start/stop the timer
    if (button2.Text == "Start")
    {
        //Change button to stop
        button2.Text = "Stop";
        //Progress used to hook the code to execute on the tick
        if (update == null)
        {
            update = new Progress<int>();
            update.ProgressChanged += UpdateUI;
            msTimer.Updater = update;
        }
        //Start the timer
        msTimer.Start();
    }
    else
    {
        //Stop the timer
        msTimer.Stop();
        //Change button to start
        button2.Text = "Start";
    }
}

Fast Timer Test UI

Maximising the MicroTimer's Code Execution Frequency

The MicroTimer's delegate is used to execute code at a rate determined by the MicroTimer's Microsecond property. Therefore, to optimize the code execution rate, the Microsecond property is set slightly larger than the known execution time of that code. See the Tek Eye article Measuring Code Execution Time in C Sharp to help determine a value for the Microseconds property. Unfortunately, code execution time can vary as Windows is not a realtime operating system, and different PCs use different speed processors. On some high end PCs, the Dice.Roll() used in this article can be run at the lowest MicroTimer setting of 1μs. This allow upto a million dice rolls to be performed per second, a 1000 times better than using the normal .NET Timer class.

A Final MicroTimer Property

The IsRunning property of the Stopwatch class can be used by a program to see if the MicroTimer is executing. This is done by exposing IsRunning in the MicroTimer:

//Stopwatch IsRunning used for a MicroTimer IsRunning property
public bool IsRunning => sw.IsRunning;

MicroTimer Class and Test Form Full Code

This is the full code for the MicroTimer class:

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace FastTimer
{
    public class MicroTimer
    {
        //The stopwatch uses the system's high performance counter
        Stopwatch sw = new Stopwatch();
        //The delegate used to call an external function
        public delegate void ExternalCode();
        public ExternalCode OnTimeout;
        //Store the timeout period in microseconds
        private int microSeconds =  100;
        //and the equivalent CPU ticks (1000000/100=10000)
        private long microSecondsInCPUTicks = Stopwatch.Frequency / 10000;
        //Accessors for the microseconds property
        //and conversion to the CPU ticks equivalent
        public int MicroSeconds
        {
            get
            {
                return microSeconds;
            }
            set
            {
                microSeconds = value;
                microSecondsInCPUTicks = value * Stopwatch.Frequency / 1000000;
            }
        }
        //Progress reporter
        public IProgress<int> Updater { get; set; }
        //Default feedback rate to a 10th of a second
        private int feedbackMilliseconds = 100;
        //in the equivalent CPU ticks
        private long feedbackSecondsInCPUTicks = Stopwatch.Frequency / 100;
        //Accessors for the feedbackMilliseconds property
        //and conversion to the CPU ticks equivalent
        public int FeedbackMilliseconds
        {
            get
            {
                return feedbackMilliseconds;
            }
            set
            {
                feedbackMilliseconds = value;
                feedbackSecondsInCPUTicks = value * Stopwatch.Frequency / 1000;
            }
        }
        //Support cancelling to stop the timer
        CancellationTokenSource stopTimer;
        //Stop the timer
        public void Stop()
        {
            if(stopTimer != null)
                stopTimer.Cancel();
        }
        //Start the MicroTimer (async for new thread)
        public async void Start()
        {
            //Start if delegate set
            if (stopTimer == null && OnTimeout != null)
            {
                //Need to be able to cancel the MicroTimer
                stopTimer = new CancellationTokenSource();
                //CPU bound task, hence Task.Run
                await Task.Run(() => Timing(Updater, stopTimer.Token));
                //When Timing function returns, finished with CancellationTokenSource
                stopTimer.Dispose();
                stopTimer = null;
            }
        }
        //Run the timer
        void Timing(IProgress<int> progress, CancellationToken cancelTimimg)
        {
            //Calculates the timing difference
            long interval;
            //Counter for progress timing
            int progressCounter = 0;
            //Start the stopwatch
            sw.Start();
            //Loop until stopped
            while (sw.IsRunning)
            {
                //Get interval to wait
                interval = sw.ElapsedTicks + microSecondsInCPUTicks;
                //Loop until interval has passed
                while (interval - sw.ElapsedTicks > 0)
                {
                    //Chance for kernel to yield the tight CPU loop
                    Thread.Sleep(0);
                }
                //Run the external code
                OnTimeout();
                //see if a progress report is required
                if (++progressCounter >= feedbackSecondsInCPUTicks / microSecondsInCPUTicks)
                {
                    progress.Report(progressCounter);
                    progressCounter = 0;
                }
                if (cancelTimimg.IsCancellationRequested)
                    sw.Stop();
            }
        }
        //Stopwatch IsRunning used for a MicroTimer IsRunning property
        public bool IsRunning => sw.IsRunning;
    }
}

The following code for FrmMicroTimerTest does not include the designer code, use the earlier form image to design the UI, the code will then be similar to this listing:

using System;
using System.Windows.Forms;
using FastTimer;

namespace MicroTimerTest
{
    public partial class FrmMicroTimerTest : Form
    {
        //Create a new MicroTimer
        MicroTimer msTimer = new MicroTimer();
        public FrmMicroTimerTest()
        {
            InitializeComponent();
            //Set the Microseconds and FeedbackMiliseconds
            //properties via the UI (NumericUpDowns)
            numericUpDown1.Value = msTimer.MicroSeconds;
            numericUpDown2.Value = msTimer.FeedbackMilliseconds;
            //Assign the function to execute at a high rate
            msTimer.OnTimeout += Dice.Roll;
        }
        private void numericUpDown1_ValueChanged(object sender, EventArgs e)
        {
            msTimer.MicroSeconds = (int)numericUpDown1.Value;
        }
        private void numericUpDown2_ValueChanged(object sender, EventArgs e)
        {
            msTimer.FeedbackMilliseconds = (int)numericUpDown2.Value;
        }
        //Test the dice roll code
        private void button1_Click(object sender, EventArgs e)
        {
            Dice.Roll();
            //Same UI Update used by Progress object
            UpdateUI(null, 0);
        }
        //Update UI with dice roll statistics
        void UpdateUI(object sender, int counter)
        {   
            label1.Text = Dice.LastRoll.ToString();
            label3.Text = Dice.Rolls[0].ToString();
            label5.Text = Dice.Rolls[1].ToString();
            label7.Text = Dice.Rolls[2].ToString();
            label9.Text = Dice.Rolls[3].ToString();
            label11.Text = Dice.Rolls[4].ToString();
            label13.Text = Dice.Rolls[5].ToString();
            label15.Text = Dice.TotalRolls.ToString();
            label17.Text = Dice.TotalValues.ToString();
            label19.Text = Dice.MeanValueRolled.ToString();
        }
        //The Progress object
        Progress<int> update;
        private void button2_Click(object sender, EventArgs e)
        {
            //Start/stop the timer
            if (button2.Text == "Start")
            {
                //Change button to stop
                button2.Text = "Stop";
                //Progress used to update UI
                if (update == null)
                {
                    update = new Progress<int>();
                    //also uses the UpdateUI function
                    update.ProgressChanged += UpdateUI;
                    //Assign the Progress object to the MicroTimer
                    msTimer.Updater = update;
                }
                //Start the timer
                msTimer.Start();
            }
            else
            {
                //Stop the timer
                msTimer.Stop();
                //Change button to start
                button2.Text = "Start";
            }
        }
    }
}

If you come across anyway to improve the code in this article then send it to Tek Eye and it can be shared with other readers. You can download the code as a Visual Studio solution in microtimer.zip.

See Also

Author:  Published:  

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.)

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