C#进程等待毫秒精确
本文关键字:进程 等待 | 更新日期: 2023-09-27 18:00:09
我正在开发一个应用程序(有点像游戏助手),它以特定的时间间隔向游戏发送击键(您可以指定要按下的键)。
问题是,我需要以毫秒的精度执行KeyPress事件。经过一些研究,我发现Thread.Sleep的分辨率为20-50毫秒,到目前为止我能找到的最好的方法是使用StopWatch(),如下所示:
cmd_PlayJumps = new DelegateCommand(
() =>
{
ActivateWindow();
Stopwatch _timer = new Stopwatch();
Stopwatch sw = new Stopwatch();
double dElapsed = 0;
//Initial Key Press
_timer.Start();
_Keyboard.KeyPress(WindowsInput.Native.VirtualKeyCode.RETURN);
int iTotalJumps = SelectedLayout.JumpCollection.Count;
//loop through collection
for (int iJump = 0; iJump < iTotalJumps - 1; iJump++)
{
dElapsed = _timer.Elapsed.TotalMilliseconds;
sw.Restart();
while (sw.Elapsed.TotalMilliseconds < SelectedLayout.JumpCollection[iJump + 1].WaitTime -
dElapsed)
{
//wait
}
_timer.Restart();
_Keyboard.KeyPress(WindowsInput.Native.VirtualKeyCode.RETURN);
}
//final key press
_Keyboard.KeyPress(WindowsInput.Native.VirtualKeyCode.RETURN);
_timer.Stop();
_timer = null;
});
由于KeyPress事件的持续时间在0.3-1.5毫秒内变化,我也会跟踪它以消除偏差。
尽管如此,我只能用这个代码获得60%的准确率,因为即使是StopWatch()也不是那么精确(当然,如果我的代码没有错误的话)。
我想知道,我如何才能达到至少90%的准确率?
问题是你需要幸运,这完全取决于达到.Tick的频率,根据你的硬件,该频率约为0.2-2毫秒。要避免这种情况是非常困难的,你可以尝试设置一个高进程优先级来窃取CPU,以获得更多的Ticks。
这可以通过以下方式实现:
System.Diagnostics.Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
同时尝试设置
while (sw.Elapsed.TotalMilliseconds <= SelectedLayout.JumpCollection[iJump + 1].WaitTime - dElapsed)
这有时会为你节省一次时间,并提高一点准确性。
除此之外,主要问题是windows本身并不是最好的Timekeeper,DateTime。例如,现在的容错率为16ms,从未被认为是"实时"操作系统。
顺便说一句:如果你真的需要这个尽可能准确,我建议你研究一下Linux。
我使用Thread.Sleep
和旋转服务员的组合,使其平均计时未命中0.448毫秒。将线程设置为高优先级不会改变逻辑,因为线程需要运行并不断检查变量。
static void Main(string[] args)
{
Thread.CurrentThread.Priority = ThreadPriority.Highest;
var timespans = new List<TimeSpan>(50);
while (timespans.Count < 50)
{
var scheduledTime = DateTime.Now.AddSeconds(0.40);
Console.WriteLine("Scheduled to run at: {0:hh:mm:ss.FFFF}", scheduledTime);
var wait = scheduledTime - DateTime.Now + TimeSpan.FromMilliseconds(-50);
Thread.Sleep((int)Math.Abs(wait.TotalMilliseconds));
while (DateTime.Now < scheduledTime) ;
var offset = DateTime.Now - scheduledTime;
Console.WriteLine("Actual: {0}", offset);
timespans.Add(offset);
}
Console.WriteLine("Average delay: {0}", timespans.Aggregate((a, b) => a + b).TotalMilliseconds / 50);
Console.Read();
}
请注意,使用在windows上运行的标准CLR代码无法获得真正的实时代码。垃圾收集器可以介入,甚至在循环周期之间,并开始收集对象,在这一点上很有可能获得不精确的时间。
您可以通过更改垃圾收集器的延迟模式来减少这种情况的发生,在这种情况下,它不会进行大的收集,直到出现内存极低的情况。如果这对你来说还不够,可以考虑用一种能更好地保证时间的语言(例如C++)编写上述解决方案。
您可以尝试使用ElapsedTicks。它是秒表可以测量的最小单位,您可以使用Frequency
属性将经过的刻度数转换为秒(当然还有秒的分数)。我不知道它是否比Elapsed.TotalMilliseconds
更好,但值得一试。