【问题标题】:How to trigger a C# function at a certain time with millisecond precision?如何在特定时间以毫秒精度触发 C# 函数?
【发布时间】:2011-07-15 09:36:23
【问题描述】:

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

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

更新:

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

似乎可以通过导入强制 Thread.Sleep() 具有 1 毫秒的分辨率:

[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(deprecated) 或 CreateTimerQueueTimer。

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

【问题讨论】:

  • 您需要附加到多媒体领域中的一些实时调度程序。标准线程切换频率约为每 40 毫秒,因此大多数基本的 Thread.Sleep 代码将无法工作。
  • 感谢您的回复。我需要一些时间来测试答案。

标签: c# winforms console


【解决方案1】:

这应该每毫秒给你一个事件。您可以使用秒表来测量经过的时间。使用 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();
    }

【讨论】:

  • 别忘了使用 timeKillEvent 否则你会在运行多个计时器的情况下让你的机器瘫痪。
  • 你能给出一个代码示例吗?我在委托、事件、事件处理程序等方面没有太多经验。
  • 只需调用 timeSetEvent 并将 tim 作为事件处理程序参数。函数 Link 将每 MilliSecond 执行一次。完成计时器后,在 timeSetEvent 的返回值上调用 timeKillEvent。
  • 我收到此错误“关键字 'this' 在当前上下文中不可用”。
  • +1 并接受,您的回答帮助我找到了我需要的解决方案 CreateTimerQueueTimer。谢谢:)
【解决方案2】:

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

【讨论】:

  • DateTime.Now 的分辨率约为 16 毫秒。它不够精确,无法用于毫秒计时。
  • @Roy Dictus:应该使用更高分辨率的计时器,如 [DllImport("winmm.dll", EntryPoint = "timeGetTime")] 并将 Thread.Sleep() 分辨率设置为 1 ms .
【解决方案3】:

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

【讨论】:

  • 你能举个很简单的例子吗?链接里的代码很大。
  • 您应该使用 .Net 'StopWatch' 类进行时间测量。
  • @Răzvan, @CodingBarfield:是的,这就是这篇文章的主要内容。它使用 QueryPerformanceCounters,由 System.Diagnostics.Stopwatch 类包装(可能是在写完文章之后添加到 .NET 2.0)。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 我知道 System.Diagnostics.Stopwatch 非常精确,但我认为它没有在特定时间触发事件的任何特定方法。所以解决方案还需要 Thread.Sleep()。
    【解决方案6】:

    怎么样:

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

    【讨论】:

    • 远不及毫秒精度。那将永远,永远,永远被迟到。睡眠睡眠“至少 x”毫秒。
    • Thread.Sleep 可以非常精确!它具有 25-40 毫秒的精度,而不是单毫秒精度。
    • 我不知道。抱歉输入了!
    猜你喜欢
    • 2020-12-02
    • 1970-01-01
    • 1970-01-01
    • 2015-09-23
    • 2014-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多