如何以毫秒级的精度在特定时间触发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。
这将使您每毫秒产生一个事件。您可以使用秒表来测量经过的时间。使用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();
}