如何以毫秒精度在特定时间触发C#函数?

时间:2011-07-15 09:36:23

标签: c# winforms console

我有2台计算机,它们的时间通过NTP同步,确保时间只相差几毫秒。其中一台计算机将通过TCP向另一台计算机发送一条消息,以便在将来的两台计算机上的指定时间启动某个c#功能。

我的问题是:如何在特定时间以毫秒精度(或更好)触发C#中的函数?我需要在程序代码中执行此操作(因此任务计划程序或其他外部程序将无济于事)。总是在单独的线程中循环以将当前时间与目标时间进行比较,这不是一个好的解决方案。

更新

DateTime.Now无法在解决方案中使用,因为它的分辨率很低。

似乎可以通过导入强制Thread.Sleep()获得1 ms的分辨率:

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

并使用:

MM_BeginPeriod(1);

要恢复到先前的分辨率导入:

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

并使用:

MM_EndPeriod(1);

更新2:

我用很多值测试了Thread.Sleep(),看起来平均值会趋向指定的时间跨度。

只调用一次Thread.Sleep()通常会在目标值时间范围内保持大约半毫秒左右,因此对于毫秒分辨率来说非常精确。

使用winmm.dll方法timeBeginPeriod和timeEndPeriod似乎对结果的准确性没有影响。

SOLUTION:

一种方法是使用timeSetEvent(不建议使用)或CreateTimerQueueTimer。

当前的问题是,两者都需要作为参数来保留直到触发功能的时间而不是它应该触发的时间。 因此必须计算触发所需时间的延迟,但DateTime.Now提供低分辨率。我找到了一个允许高分辨率获取当前DateTime的类。 所以现在剩下的时间可以用高分辨率计算,并作为参数传递给CreateTimerQueueTimer。

6 个答案:

答案 0 :(得分:3)

这应该每毫秒给你一个事件。您可以使用秒表来测量经过的时间。使用调用在主UI线程上触发事件,这样就不会阻止计时器。

    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();
    }

答案 1 :(得分:1)

.NET 4内置了用于并行处理的任务调度。

你可以这样写:

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);
}

答案 2 :(得分:1)

Peter A. Bromberg撰写了一篇关于High-Precision Code Timing in .NET的文章。轮询DateTime不起作用,因为DateTime.Now的分辨率相对较低(大约16毫秒)。

答案 3 :(得分:0)

可能你应该按照System.Threading.Timer中的说明使用this article,但根据你的需要,其中描述的其他两个计时器类也可能是有效的选择。

但是,我不知道Windows计时器的精确保证是什么 - 您应该测试它是否足以满足您的需求(并且请记住它可能因机器而异)。

答案 4 :(得分:0)

如果您应该在特定时间触发该功能,也许您可​​以使用Stopwatch class。我认为使用计时器不够安全,因为无法保证已用事件的时间。

答案 5 :(得分:-1)

怎么样:

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