简体   繁体   中英

How to trigger a C# function at a certain time with millisecond precision?

I have 2 computers whose time is synchronized through NTP which assures that the time will differ only by a few milliseconds. One of the computers will send a message through TCP to the other one to start a certain c# function at a specified time in the future on both computers.

My question is: How can i trigger a function in C# at a certain time with millisecond precision (or better)? I need to do that in the program code ( so Task Scheduler or other external program will not help). Always cycling in a separate thread to compare current time with target time would not be a good solution i guess.

UPDATE:

DateTime.Now can't be used in the solution since it has low resolution.

It seems Thread.Sleep() can be forced to have a 1 ms resolution by importing:

[DllImport("winmm.dll", EntryPoint="timeBeginPeriod")]
public static extern uint MM_BeginPeriod(uint uMilliseconds);

and using:

MM_BeginPeriod(1);

To revert to the previous resolution import:

[DllImport("winmm.dll", EntryPoint = "timeEndPeriod")]
public static extern uint MM_EndPeriod(uint uMilliseconds);

and use:

MM_EndPeriod(1);

UPDATE 2:

I tested Thread.Sleep() with many values and it seems that as an average it will tend towards the specified time-span.

Calling Thread.Sleep() only once usually stays around half a ms around the target value time-span so it is pretty precise regarding to a millisecond resolution.

Using the winmm.dll methods timeBeginPeriod and timeEndPeriod seems to have no effect on the accuracy of the result.

SOLUTION:

One method would be to use timeSetEvent(deprecated) or CreateTimerQueueTimer.

Current problem is that both need as a parameter the time left untill function triggers instead of the time at which it should trigger. So the delay until desired time for the trigger must be calculated but DateTime.Now offers low resolution. I found a class that allows high resolution obtaining of current DateTime. So now the time left can be calculated with high resolution and be passed as a parameter to CreateTimerQueueTimer.

This should give you one event every millisecond. You can use a stopwatch to measure the elapsed time. Trigger an event on the main UI thread by using invoke so you don't block the timer.

    public delegate void TimerEventHandler(UInt32 id, UInt32 msg, ref UInt32 userCtx, UInt32 rsv1, UInt32 rsv2);

    /// <summary>
    /// A multi media timer with millisecond precision
    /// </summary>
    /// <param name="msDelay">One event every msDelay milliseconds</param>
    /// <param name="msResolution">Timer precision indication (lower value is more precise but resource unfriendly)</param>
    /// <param name="handler">delegate to start</param>
    /// <param name="userCtx">callBack data </param>
    /// <param name="eventType">one event or multiple events</param>
    /// <remarks>Dont forget to call timeKillEvent!</remarks>
    /// <returns>0 on failure or any other value as a timer id to use for timeKillEvent</returns>
    [DllImport("winmm.dll", SetLastError = true,EntryPoint="timeSetEvent")]
    static extern UInt32 timeSetEvent(UInt32 msDelay, UInt32 msResolution, TimerEventHandler handler, ref UInt32 userCtx, UInt32 eventType);

    /// <summary>
    /// The multi media timer stop function
    /// </summary>
    /// <param name="uTimerID">timer id from timeSetEvent</param>
    /// <remarks>This function stops the timer</remarks>
    [DllImport("winmm.dll", SetLastError = true)]
    static extern void timeKillEvent(  UInt32 uTimerID );

    TimerEventHandler tim = new TimerEventHandler(this.Link);
    public void Link(UInt32 id, UInt32 msg, ref UInt32 userCtx, UInt32 rsv1, UInt32 rsv2)
    {
        _counter++;
        if( (_counter % 10 ) == 0)
            setLblTxt();
    }

.NET 4 comes with task scheduling built-in for parallel processing.

You could write something like this:

void QueueIt(long tick)
{
    Task workTask = new Task(() => MyMethod());

    Task scheduleTask = Task.Factory.StartNew( () =>
    {                
        WaitUtil(tick); // Active waiting
        workTask.Start();
    });
}


void WaitUntil(long tick)
{
    var toWait = tick - DateTime.Now.Ticks;
    System.Threading.Thread.Sleep(toWait);
}

There is an article from Peter A. Bromberg about High-Precision Code Timing in .NET . Polling DateTime will not work because DateTime.Now has a relatively low resolution (around 16ms).

Probably you should use System.Threading.Timer as explained in this article , but depending on your needs the other two timer classes described therein might also be valid choices.

However, I don't know what the precision guarantees are for the Windows timers -- you should test that it is sufficient for your needs (and keep in mind that it may vary from one machine to the other).

Maybe you can use the Stopwatch class if you should trigger the function at a specific time. I think using a timer is not secure enough, as the timing of the elapsed events are not guaranteed.

What about:

public void ReceivesIncomingTCP(DateTime startDate)
{
Thread.Sleep(startDate.Subtract(DateTime.Now).Milliseconds)
DoAction();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM