Microsecond and Millisecond C# Timer
Introduction
Anyone who has used the .NET System.Timers.Timer
class for low interval times will realise that it does not offer a very high resolution. The resolution will be system dependant, but a maximum resolution is usually around 15ms (System.Windows.Forms.Timer
has an even worse resolution, although it is unlikely a UI will need to be updated this fast). Significantly better performance can be achieved using the Win32 multimedia timer (there are various .NET projects that wrap this timer); however, there are no timers available in the microsecond range.
The problem I encountered was that I needed to send an Ethernet UDP message packet out every 800µs (0.8ms); it did not matter if a packet was slightly delayed or did not go off exactly 800µs after the last one. Basically, what I needed was a microsecond timer that was accurate the majority of the time.
The fundamental problem with a software timer in the region of 1ms is that Windows is a non real-time Operating System (RTOS), and is not suitable for generating regular and accurate events around the 1ms mark. MicroTimer cannot and does not solve this problem; however, it does offer a microsecond timer which offers a reasonable degree of accuracy (approx. 1µs) the majority (approx. 99.9%) of the time. The trouble is, the 0.1% of the time, the timer can be very inaccurate (whilst the Operating System gives some of the processing time to other threads and processes). The accuracy is highly system/processor dependant; a faster system will result in a more accurate timer.
The beauty of MicroTimer is that it is called in a very similar way to the existing System.Timers.Timer
class; however, the interval is set in microseconds (as opposed to milliseconds in System.Timers.Timer
). On each timed event, MicroTimer invokes the predefined (OnTimedEvent
) callback function. The MicroTimerEventArgs
properties provide information (to the microsecond) on when exactly (and how late) the timer was invoked.
Using the code
'MicroLibrary.cs' encompasses three classes (under the namespace 'MicroLibrary
'):
MicroStopwatch
- This derives from and extends theSystem.Diagnostics.Stopwatch
class; importantly, it provides the additional property 'ElapsedMicroseconds
'. This is useful as a standalone class where the elapsed microseconds from when the stopwatch was started can be directly obtained.MicroTimer
- Designed so it operates in a very similar way to theSystem.Timers.Timer
class, it has a timer interval in microseconds, and aStart
andStop
method (orEnabled
property). It implements a custom event handler (MicroTimerElapsedEventHandler
) that fires every interval. The 'NotificationTimer
' function is where the 'work' is done, and is run in a separate high priority thread. It should be noted thatMicroTimer
is inefficient and very processor hungry as the 'NotificationTimer
' function runs a tightwhile
loop until the elapsed microseconds is greater than the next interval. This is not ideal; however, for such small intervals, this is probably the only practical solution.MicroTimerEventArgs
- Derived fromSystem.EventArgs
, this class provides an object for holding information about the event. Namely, the number of times the event has fired, the absolute time (in microseconds) from when the timer was started, how late the event was, and also the execution time of the callback function (for the previous event). From this data, a range of timer information can be derived.
By design, the amount of work done in the callback function (OnTimedEvent
) must be small (e.g., update a variable or fire off a UDP packet). To that end, the work done in the callback function must take significantly less time than the timer interval. Separate threads could be spawned for longer tasks; however, this goes outside the scope of this article. As discussed earlier, because Windows is not a real time Operating System, the callback function (OnTimedEvent
) may be late; if this happens and any particular interval is delayed, there are two options:
Either
: Set the property 'IgnoreEventIfLateBy
' whereby the callback function (OnTimedEvent
) will not be called if the timer is late by the specified number of microseconds. The advantage of this is the timer will not attempt to 'catch up', i.e., it will not call the callback function in quick succession in an attempt to catch up. The disadvantage is that some events will be missed.Or
: By default, MicroTimer will always try and catch up on the next interval. The advantage of this is the number of times theOnTimeEvent
is called will always be correct for the total elapsed time (which is why theOnTimedEvent
must take significantly less time than the interval; if it takes a similar or longer time, MicroTimer can never 'catch up' and the timer event will always be late). The disadvantage of this is when it's trying to 'catch up', the actual interval achieved will be much less than the required interval as the callback function is called in quick succession in an attempt to catch up.
The code below shows the 'MicroLibrary
' namespace (MicroLibrary.cs) which contains the three classes, MicroStopwatch
, MicroTimer
, and MicroTimerEventArgs
. See the 'Download source' link above.
using System;
namespace MicroLibrary
{
/// <summary>
/// MicroStopwatch class
/// </summary>
public class MicroStopwatch : System.Diagnostics.Stopwatch
{
readonly double _microSecPerTick = 1000000D / Frequency;
public MicroStopwatch()
{
if (!System.Diagnostics.Stopwatch.IsHighResolution)
{
throw new Exception("On this system the high-resolution " +
"performance counter is not available");
}
}
public long ElapsedMicroseconds
{
get
{
return (long)(ElapsedTicks * _microSecPerTick);
}
}
}
/// <summary>
/// MicroTimer class
/// </summary>
public class MicroTimer
{
public delegate void MicroTimerElapsedEventHandler(
object sender,
MicroTimerEventArgs timerEventArgs);
public event MicroTimerElapsedEventHandler MicroTimerElapsed;
System.Threading.Thread _threadTimer = null;
long _ignoreEventIfLateBy = long.MaxValue;
long _timerIntervalInMicroSec = 0;
bool _stopTimer = true;
public MicroTimer()
{
}
public MicroTimer(long timerIntervalInMicroseconds)
{
Interval = timerIntervalInMicroseconds;
}
public long Interval
{
get { return _timerIntervalInMicroSec; }
set { _timerIntervalInMicroSec = value; }
}
public long IgnoreEventIfLateBy
{
get
{
return _ignoreEventIfLateBy;
}
set
{
if (value <= 0)
_ignoreEventIfLateBy = long.MaxValue;
else
_ignoreEventIfLateBy = value;
}
}
public bool Enabled
{
set
{
if (value)
Start();
else
Stop();
}
get
{
return (_threadTimer != null && _threadTimer.IsAlive);
}
}
public void Start()
{
if (Enabled || Interval <= 0)
{
return;
}
_stopTimer = false;
System.Threading.ThreadStart threadStart = delegate()
{
NotificationTimer(Interval, IgnoreEventIfLateBy, ref _stopTimer);
};
_threadTimer = new System.Threading.Thread(threadStart);
_threadTimer.Priority = System.Threading.ThreadPriority.Highest;
_threadTimer.Start();
}
public void Stop()
{
_stopTimer = true;
if (_threadTimer.ManagedThreadId ==
System.Threading.Thread.CurrentThread.ManagedThreadId)
{
return;
}
while (Enabled)
{
System.Threading.Thread.SpinWait(10);
}
}
void NotificationTimer(long timerInterval,
long ignoreEventIfLateBy,
ref bool stopTimer)
{
int timerCount = 0;
long nextNotification = 0;
MicroStopwatch microStopwatch = new MicroStopwatch();
microStopwatch.Start();
while (!stopTimer)
{
long callbackFunctionExecutionTime =
microStopwatch.ElapsedMicroseconds - nextNotification;
nextNotification += timerInterval;
timerCount++;
long elapsedMicroseconds = 0;
while ( (elapsedMicroseconds = microStopwatch.ElapsedMicroseconds)
< nextNotification)
{
System.Threading.Thread.SpinWait(10);
}
long timerLateBy = elapsedMicroseconds - (timerCount * timerInterval);
if (timerLateBy >= ignoreEventIfLateBy)
{
continue;
}
MicroTimerEventArgs microTimerEventArgs =
new MicroTimerEventArgs(timerCount,
elapsedMicroseconds,
timerLateBy,
callbackFunctionExecutionTime);
MicroTimerElapsed(this, microTimerEventArgs);
}
microStopwatch.Stop();
}
}
/// <summary>
/// MicroTimer Event Argument class
/// </summary>
public class MicroTimerEventArgs : EventArgs
{
// Simple counter, number times timed event (callback function) executed
public int TimerCount { get; private set; }
// Time when timed event was called since timer started
public long ElapsedMicroseconds { get; private set; }
// How late the timer was compared to when it should have been called
public long TimerLateBy { get; private set; }
// Time it took to execute previous call to callback function (OnTimedEvent)
public long CallbackFunctionExecutionTime { get; private set; }
public MicroTimerEventArgs(int timerCount,
long elapsedMicroseconds,
long timerLateBy,
long callbackFunctionExecutionTime)
{
TimerCount = timerCount;
ElapsedMicroseconds = elapsedMicroseconds;
TimerLateBy = timerLateBy;
CallbackFunctionExecutionTime = callbackFunctionExecutionTime;
}
}
}
The code below shows a very simple (console application) implementation of the MicroTimer
class with the interval set to 1,000µs (1ms). See the 'Download demo project' link above.
using System;
namespace MicroTimerConsoleDemo
{
class Program
{
static void Main(string[] args)
{
Program program = new Program();
program.MicroTimerTest();
}
private void MicroTimerTest()
{
// Instantiate new MicroTimer and add event handler
MicroLibrary.MicroTimer microTimer = new MicroLibrary.MicroTimer();
microTimer.MicroTimerElapsed +=
new MicroLibrary.MicroTimer.MicroTimerElapsedEventHandler(OnTimedEvent);
microTimer.Interval = 1000; // Call micro timer every 1000µs (1ms)
// Can choose to ignore event if late by Xµs (by default will try to catch up)
// microTimer.IgnoreEventIfLateBy = 500;
microTimer.Enabled = true; // Start timer
// Do something whilst events happening, for demo sleep 2000ms (2sec)
System.Threading.Thread.Sleep(2000);
microTimer.Enabled = false; // Stop timer
// Wait for user input
Console.ReadLine();
}
private void OnTimedEvent(object sender,
MicroLibrary.MicroTimerEventArgs timerEventArgs)
{
// Do something small that takes significantly less time than Interval
Console.WriteLine(string.Format(
"Count = {0:#,0} Timer = {1:#,0} µs, " +
"LateBy = {2:#,0} µs, ExecutionTime = {3:#,0} µs",
timerEventArgs.TimerCount, timerEventArgs.ElapsedMicroseconds,
timerEventArgs.TimerLateBy, timerEventArgs.CallbackFunctionExecutionTime));
}
}
}
The screenshot below shows the console output. The performance varies on different runs, but was usually accurate to 1µs. Due to system caching, the accuracy was worse on the first run and got better after the first few events. This test was on a 2GHz Dell Inspiron 1545 with an Intel Core 2 Duo (running Windows 7 64bit). The performance improved significantly on faster machines.
Summary
MicroTimer is designed for situations were a very quick timer is required (around the 1ms mark); however, due to the non real-time nature of the Windows Operating System, it can never be accurate. However, as no other microsecond software timers are available, it does offer a reasonable solution for this task (and although processor hungry, is reasonably accurate on fast systems).
History
- 31 July 2010 - Article submitted.
发表评论
ウブロブランドコピー時計
卸売各種偽物ブランド:ロレックス時計,
カルティエ時計,オメガ時計,
ウブロ時計などの世界にプランド商品です。
当店のブランドコピー 時計(N級品)は本物
と同じ素材を採用しています。
ブランドコピー,本物を真似た偽物.模造!激安市場激安屋】2018新品は発売します !
サイトは世界一流ブランド コピー 専門店です。
ぜひ一度当店の商品をお試しください。
驚きと満足を保証致します。
ご利用をお待ちしております。
スーパーコピーブランド専門店
スーパーコピーブランド 代引き/スーパ
コピーブランド専門店
口コミスーパーコピーブランド
時計スーパーコピーブランド
国内発送n級品
スーパーコピーブランド
スーパーコピーブランド
スーパーコピーブランドn品
スーパーコピーブランドバッグ
スーパーコピーブランド 口コミ
ブランドコピー 激安屋
ブランドコピー バッグ
ブランドコピー 財布
ブランドコピー 時計
ブランドコピー 靴
ブランドコピー サングラス
ブランドコピー ベルト
ブランドコピー ネックレス
ブランドコピー 服
ブランドコピー Tシャツ
ブランドコピー 販売
本物品質で安心と信頼のブランドコピー専門ショップです。
激安屋
ブランドコピー唯一の公式サイト!
ご安心してお
ブランド コピー 激安屋
2018年人気最新品、財布最新品
高品質な商品を超格安価格で、安心、迅速
サイトは世界一流ブランド コピー 専門店です。
ぜひ一度当店の商品をお試しください。
驚きと満足を保証致します。
ご利用をお待ちしております。綺麗な梱包でした。明細書をきちんと入れてあり、とても感じよかったです。でも、商品については、斜め掛けは難しく、肩掛けの方が良いと説明があると、うれしかったです。100センチとあったので、こちらも勘違いしました。
F7Ijne Thanks for sharing, this is a fantastic post.Really thank you! Fantastic.
cOlXCT This is one awesome blog article.Really looking forward to read more. Really Great.
Ugireojfe whfiwehfjwehwhfjehfwefhweh 777uiop fweh iwehf weiohf wieohf iwehf iweyu59tu328hfire iuwfodhqw934785 h3urh9wjfwgut h9wh9889wh98r h4wt93qrj29th2 rj2ghw9tfq.
Pgksrjgiohi hw hweokfjeq ojfe jfweiogwo gwoj wijf gdhgtrj575 y6u75tyhgf 5yu5regr
FFSYLj Thanks for sharing, this is a fantastic blog.Really looking forward to read more. Fantastic.
7TnggG Very neat blog article.Thanks Again. Great.
7XWRO4 later than having my breakfast coming again to
AeP5IQ Incredible! This blog looks just like my old one! It as on a completely different subject but it has pretty much the same page layout and design. Great choice of colors!
I truly appreciate this blog post.Much thanks again. Much obliged.
B1LYf6 Really informative blog post.Much thanks again. Cool.
QReJDO Wanted to drop a remark and let you know your Rss feed is not working today. I tried adding it to my Yahoo reader account but got nothing.
iFkLTU You can certainly see your skills in the work you write. The world hopes for more passionate writers such as you who aren at afraid to say how they believe. At all times follow your heart.
fDUKm8
j1dDsx the content. You are an expert in this topic! Take a look at my web blog Expatriate life in Spain (Buddy)
NWQfFt Thanks again for the post.Really looking forward to read more. Will read on
P6R2LE Wow, great blog article.Really thank you! Great.
F2IZYS I truly appreciate this blog post. Keep writing.
q2oYhA Hey, thanks for the blog post. Great.
AoWiqQ Very good blog post.Really thank you! Really Cool.
H7DHrX Very good written story. It will be helpful to everyone who usess it, as well as myself. Keep up the good work - looking forward to more posts.
cQzQTv There is perceptibly a bunch to know about this. I think you made various nice points in features also.
4GQ8tV I love what you guys are usually up too. This sort of clever work and exposure! Keep up the excellent works guys I've included you guys to my blogroll.
S6ajpV I'm often to blogging and i actually appreciate your content. The article has really peaks my interest. I am going to bookmark your site and maintain checking for new information.
6XtVbT Thank you, I've just been searching for information about this subject for ages and yours is the greatest I have discovered so far. But, what about the bottom line? Are you sure about the source?
mV95gt Enjoyed every bit of your blog post.Really looking forward to read more. Great.
pxOv0C Thanks for sharing, this is a fantastic article.Really looking forward to read more. Keep writing.
KRAlbC Thanks so much for the article post.Really looking forward to read more. Want more.
efrbOG Thank you ever so for you blog.Really looking forward to read more. Much obliged.
RlfAdn Thanks again for the post.Really looking forward to read more. Really Great.
eJmlim I value the blog.Really thank you! Fantastic.
1uKg9R I really liked your article. Great.
KlqfgR A big thank you for your article post.Really looking forward to read more. Will read on...
.!WeA Zm$EY Y/bct ,&316 &!Kc-
XJqpM I-"Ab Qe>Yq t6OGe q\9oc
GmBON o\T;X Oz3zP HeFHy Pr899
,".uM WI?Zb gt"?^ oVCZq a(f^M
T+zkX R\ZD3 Sxg_z 222;# ht2@g
&\x&e xnZN< wt$J" lb#[+ )pcLv
k$?2- UAKhj yOIT1 zT55u 9X-oH
_xz`z \D;zC ;[>:a R-v*5 (m@R\
[E7N4 [zxx' g`0R! 42c:+ vBhPj
w!<rA _#eG; 'NY<F Z/R - u Agnyd
muRqsk Appreciate you sharing, great blog article.Really thank you! Great.
UHx3I6 Im grateful for the blog post.Really thank you!