如何以毫秒级的精度在特定时间触发c#函数

本文关键字:定时间 函数 精度 | 更新日期: 2023-09-27 18:02:31

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

我的问题是:我如何在c#中触发一个函数在特定的时间与毫秒精度(或更好)?我需要在程序代码中这样做(所以任务调度程序或其他外部程序不会有帮助)。总是在一个单独的线程中循环来比较当前时间和目标时间,我想这不是一个好的解决方案。

更新:

DateTime。现在不能在溶液中使用,因为它的分辨率很低。

似乎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似乎对结果的准确性没有影响。

解决方案:

一个方法是使用timeSetEvent(已弃用)或CreateTimerQueueTimer。

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

如何以毫秒级的精度在特定时间触发c#函数

这将使您每毫秒产生一个事件。您可以使用秒表来测量经过的时间。使用invoke在主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();
    }

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

Peter A. Bromberg有一篇关于。net中高精度代码时序的文章。轮询DateTime将无法工作,因为DateTime.Now具有相对较低的分辨率(约16ms)。

也许您应该使用本文中解释的System.Threading.Timer,但根据您的需要,其中描述的其他两个定时器类也可能是有效的选择。

然而,我不知道Windows计时器的精度保证是什么——你应该测试它是否足以满足你的需要(并记住它可能因机器而异)。

如果需要在特定时间触发该函数,可以使用Stopwatch类。我认为使用计时器不够安全,因为经过的事件的计时不能得到保证。

怎么样:

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