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

如何在特定时间以毫秒精度触发C#函数?,c#,winforms,console,C#,Winforms,Console,我有两台计算机,它们的时间是通过NTP同步的,NTP可以确保时间只相差几毫秒。其中一台计算机将通过TCP向另一台计算机发送消息,以便在将来的指定时间在两台计算机上启动某个c#函数 我的问题是:如何在特定时间以毫秒精度(或更好)触发C#中的函数?我需要在程序代码中这样做(因此任务调度器或其他外部程序将不会有帮助)。我想,总是在一个单独的线程中循环以比较当前时间和目标时间不是一个好的解决方案 更新: DateTime.Now无法在解决方案中使用,因为它的分辨率较低 似乎可以通过导入以下内容强制Thr

我有两台计算机,它们的时间是通过NTP同步的,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(已弃用)或CreateTimerQueueTimer

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

可能您应该使用中所述的方法,但根据您的需要,其中描述的其他两个计时器类也可能是有效的选择

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

关于:

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

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有一篇文章是关于。轮询
DateTime
将不起作用,因为
DateTime。现在
的分辨率相对较低(约16ms)。

这将使您每毫秒发生一次事件。你可以用秒表来测量经过的时间。使用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();
    }
public delegate void TimerEventHandler(UInt32 id,UInt32 msg,ref UInt32 userCtx,UInt32 rsv1,UInt32 rsv2);
/// 
///毫秒级多媒体定时器
/// 
///每延迟毫秒一个事件
///计时器精度指示(值越低越精确,但对资源不友好)
///委托开始
///回调数据
///一个事件或多个事件
///别忘了打电话给timeKillEvent!
///0或任何其他值作为timeKillEvent使用的计时器id
[DllImport(“winmm.dll”,SetLastError=true,EntryPoint=“timeSetEvent”)]
静态外部UInt32时间事件(UInt32 msDelay、UInt32 msResolution、TimerEventHandler处理程序、ref UInt32 userCtx、UInt32事件类型);
/// 
///多媒体定时器停止功能
/// 
///来自timeSetEvent的计时器id
///此函数用于停止计时器
[DllImport(“winmm.dll”,SetLastError=true)]
静态外部无效timeKillEvent(UInt32 uTimerID);
TimerEventHandler tim=新的TimerEventHandler(this.Link);
公共无效链接(UInt32 id、UInt32 msg、参考UInt32 userCtx、UInt32 rsv1、UInt32 rsv2)
{
_计数器++;
如果(_计数器%10)=0)
setLblTxt();
}

您需要连接到多媒体领域中的一些实时调度器。标准的线程切换频率大约是每4000万秒一次,所以最基本的线程。睡眠代码不会工作。谢谢回复。我需要一些时间来测试答案,精度远不及毫秒。那将永远,永远,永远被称为迟到。Sleep睡眠“至少x”毫秒。Thread.Sleep可以精确到!它有25-40毫秒的精度,不是一毫秒的精度。我不知道。很抱歉输入了<代码>日期时间。现在的分辨率约为16毫秒。它不够精确,无法用于毫秒计时。@Roy Dictus:应该使用更高分辨率的计时器,如[DllImport(“winmm.dll”,EntryPoint=“timeGetTime”)]并设置Thread.Sleep()分辨率为1毫秒。别忘了使用timeKillEvent,否则在多个计时器运行的情况下,您的机器会崩溃。您能给出一个代码示例吗?我在委托、事件、事件处理程序等方面没有太多经验。只需使用tim作为eventhandler参数调用timeSetEvent即可。功能链接将每毫秒执行一次。使用完计时器后,在timeSetEvent的returnvalue上调用timeKillEvent。我收到此错误“关键字“this”在当前上下文中不可用”。+1并被接受,您的回答帮助我找到了CreateTimerQueueTimer,这正是解决方案所需的。谢谢:)我知道System.Diagnostics.Stopwatch非常精确,但我认为它没有在特定时间触发事件的任何特定方法。因此,解决方案还需要Thread.Sleep()。您能给出一个非常简单的示例吗?链接中的代码非常大。你应该使用