Introduction

It is has been widely toted that Microsecond Precision (μs) scale time is not possible on .NET or Mono due to many issues which I will not endeavor into explaining.

Based on some of this I originally had setup a task for myself to write a good portable μs scale timer which performed the necessary platform invocation.

After I was done I realized that there is nothing "scientifically" stopping .NET from having this precision based on the fact that the caller executes the invocation and obtains the result and the GC cannot interrupt platform invocation calls so long as you do not pass a
managed type.

E.g., if I pass a plain old pointer to a un-managed function there is nothing for the GC to interrupt or stop unless the Kernel itself interrupts the call for something.

I originally though about using unsafe code but then I realized that I was just going closer to using platform invocation.

I thought about trying to obtain precise clock cycles using a static constructor which forced a GC and then ran to determine things like call overhead and whatnot but I felt that there was more time being spent on trying to obtain information then actually sleeping for the user which was the goal.

I then realized something even more bold and interesting... Sockets have a microsecond precision due to signaling and they are usable from the .NET Framework and there is a Poll method which actually accepts the amount of time in Microseconds (μs).

After some quick tests I realized I had something which was a lightweight sealed class with all static members with no more resources than a single socket.

I tricked the socket into always being busy and then I used the Poll method to obtain the desired sleep time in Microsecond Precision (μs).

I want to know what everyone thinks about this and if anyone sees anything glaring out at me which I did not also take into account.

Here is the class code and testing code complete with platform invocation methods (found here on Stack Overflow @ usleep is obsolte...) for comparison and testing.

#region Cross Platform μTimer
///<summary>/// A Cross platform implementation which can delay time on the microsecond(μs) scale.
/// It operates at a frequencies which are faster then most Platform Invoke results can provide due to the use of Kernel Calls under the hood.
/// Requires Libc.so@usleep on Mono and QueryPerformanceCounter on Windows for uSleep static
///</summary>///<notes>A Tcp Socket will be created on port 7777 by default to help keep track of time. No connections will be recieved from this socket.</notes>publicsealedclass μTimer : IDisposable
{
#region Not Applicable for the MicroFramework
#if(!MF)
#region Uncesessary Interop (Left for Comparison)
#if MONO
using System.Runtime.InteropServices;
[System.Runtime.InteropServices.DllImport("libc.so")] //.a , Not Portablestaticexternint usleep (uint amount);
///<notes>The type useconds_t is an unsigned integer type capable of holding integers in the range [0,1000000]. Programs will be more portable if they never mention this type explicitly. </notes>void uSleep(int waitTime) { usleep(waitTime); }
#else
[System.Runtime.InteropServices.DllImport("Kernel32.dll")]
staticexternbool QueryPerformanceCounter(outlong lpPerformanceCount);
[System.Runtime.InteropServices.DllImport("Kernel32.dll")]
staticexternbool QueryPerformanceFrequency(outlong lpFrequency);
///<summary>/// Performs a sleep using a plaform dependent but proven method
///</summary>///<paramname="amount">The amount of time to sleep in microseconds(μs)</param>publicstaticvoid uSleep(TimeSpan amount) { μTimer.uSleep(((int)(amount.TotalMilliseconds * 1000))); }
///<summary>/// Performs uSleep by convention of waiting on performance couters
///</summary>///<paramname="waitTime">The amount of time to wait</param>publicstaticvoid uSleep(int waitTime)
{
long time1 = 0, time2 = 0, freq = 0;
QueryPerformanceCounter(out time1);
QueryPerformanceFrequency(out freq);
do
{
QueryPerformanceCounter(out time2);
} while ((time2 - time1) < waitTime);
}
#endif
#endregion
#endif
#endregion
#region Statics
//Who but meconstushort Port = 7777;
//Since System.Timespan.TickerPerMicrosecond is constantly 10,000publicconstlong TicksPerMicrosecond = 10;
///<summary>/// A divider used to scale time for waiting
///</summary>publicconstlong Divider = TimeSpan.TicksPerMillisecond / TicksPerMicrosecond;
staticbool m_Disposed;
///<summary>/// The socket we use to keep track of time
///</summary>static Socket m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
///<summary>/// The memory we give to the socket for events which should not occur
///</summary>static SocketAsyncEventArgs m_SocketMemory = new SocketAsyncEventArgs();
publicstatic DateTime LocalTime { get { returnnew DateTime(Environment.TickCount * TimeSpan.TicksPerMillisecond); } }
publicstatic DateTime UniversalTime { get { return LocalTime.ToUniversalTime(); } }
///<summary>/// Handles the creation of resources used to provide the μSleep method.
///</summary>static μTimer()
{
try
{
//Listen on the Loopback adapter on the specified port
m_Socket.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.Loopback, Port));
//Only for 1 client
m_Socket.Listen(1);
//Assign an event now because in Begin process we will not call it if the even will not raise
m_SocketMemory.Completed += BeginProcess;
#if(!MF)
//If the SocketAsyncEventArgs will not raise it's own event we will call it nowif (!m_Socket.AcceptAsync(m_SocketMemory))
{
BeginProcess(typeof(μTimer), m_SocketMemory);
}
#else
new Thread(()=> BeginProcess(this, null)).Start();
#endif
}
catch
{
throw;
}
}
///<summary>/// Handles processing on the master time socket.
/// This should never occcur.
///</summary>///<paramname="sender">The sender of the event</param>///<paramname="e">The SocketAsyncEventArgs from the event</param>///<remarks>/// No one should connect... Ever.. (This is not a signaling implementation)
///</remarks>#if(!MF)
staticvoid BeginProcess(object sender, SocketAsyncEventArgs e)
{
#else
staticvoid BeginProcess(object sender, object args e)
{
while(!m_Disposed)
{
try
{
Socket dontCare = m_Socket.Accept(); dontCare.Dispose();
thrownew System.InvalidProgramException("A Connection to the system was made by a unauthorized means.");
}
catch { throw; }
}
#endif
if (!m_Disposed && e.LastOperation == SocketAsyncOperation.Connect)
{
try
{
thrownew System.InvalidProgramException("A Connection to the system was made by a unauthorized means.");
}
finally
{
if (e.AcceptSocket != null)e.AcceptSocket.Dispose();
}
}
}
///<summary>/// Performs a sleep using a method engineered by Julius Friedman (juliusfriedman@gmail.com)
///</summary>///<paramname="amount">The amount of time to Sleep</param>publicstaticvoid μSleep(TimeSpan amount)
{
//Sample the system clock
DateTime now = μTimer.UniversalTime, then = μTimer.UniversalTime;
TimeSpan waited = now - then;
//If cpu time is not fast enough to accomadate then you are in bigger trouble then you knowif (waited > amount) return;
else System.Threading.Thread.Sleep(amount - waited); //A normal sleep with an amount less that 1 but greater than 0 Millisecond will not switch
waited = now - then;//Waste cycles and calculate time waited in ticks againif (waited > amount) return;
elseunchecked
{
//Scale time, basis of theory is we shouldn't be able to read from a socket in Accept mode //and it should take more time than a 1000th of the time we needif (m_Socket.WaitRead(((int)((amount.Ticks - waited.Ticks / TicksPerMicrosecond) / Divider))))
{
//We didn't sleep//Sample the system clock
then = μTimer.UniversalTime;
//Calculate waited//Subtract time already waited from amount
amount -= waited;
//Waited set to now - then to determine wait
waited = now - then;
//return or utilize rest of slice sleepingif (waited > amount) return;
else System.Threading.Thread.Sleep(amount - waited);
}
}
}
///<summary>/// Performs a sleep using a method engineered by Julius Friedman (juliusfriedman@gmail.com)
///</summary>///<paramname="amount">The amount of time to Sleep in microseconds(μs) </param>publicstaticvoid μSleep(int amount) { μTimer.μSleep(TimeSpan.FromMilliseconds(amount * TimeSpan.TicksPerMillisecond)); }
#endregion
void IDisposable.Dispose()
{
m_Disposed = true;
if (m_Socket != null)
{
m_Socket.Dispose();
m_Socket = null;
}
}
}
#endregion

Here is the testing code:

I even updated it to show that the StopWatch verifies that my method sleeps for under 1 μs.

Test Failed! (A Result of the writenotice function, take only note of the color which will be "DarkGreen"

Exception.Message: StopWatch Elapsed during µSleep = 00:00:00.0000043

µTimer Took: 00:00:00

PerformanceCounter Took: 00:00:00

StopWatch Took 00:00:00.0000006

Test Passed! (Because my method was faster)

Test Passed! (Because my method was faster)

Press (W) to run again, (D) to debug or any other key to continue. (Press D or Q)

0 Failures, 7778 Successes (Because the CPU Has to warm up the first executions I believe)

On of the key points being: "Chipset vendors should implement an HPET to comply with Intels "IA-PC HPET (High Precision Event Timers) Specification"
and this code is strengthened on the reliance that all NIC processors can handle this requirement quite easily!

Some will say this is a hack and a shortcut but in all honesty, is there anything really wrong with this?

If you need the 'WaitRead' Method code you can check out Manged Media Aggregation which will be releasing this code very shortly along with a bunch of other goodies!

Until then use Poll with the same value as I do in 'WaitRead' and SelectMode.Read

I hope you agree now and hopefully this will show you that there is nothing scientifically wrong with the notion in the first place... My example was fine in the beginning and the hard part seemed to be getting you to understand and fixing your code to be correct but I do appreciate your rebuttal it just made me even more sure I was correct!

Thanks for all of your effort and let me know if you there is anything else you need proof wise!

I would avoid using the micro symbol in your namespace and function names. This requires the developer, if they don't know the key code sequence by heart, to look it up in Charmap or on an ASCII chart. If they are not using the standard fonts, the extended character may be different than the micro character, and opening it in a text editor that doesn't support it may cause compilation errors later on.

I would be very frustrated every time I wanted to use your library if I had to open another program or look up key codes to add your namespace in the code editor. When typing code, I'm typically faster than intellisense in getting the first couple letters out so don't make the user rely on intellisense (may not be available in the editing package they use, or require them to search through a list of function names or namespaces to add it).