C#进程等待毫秒精确

时间:2015-06-26 13:58:22

标签: c# .net timer precision

我正在开发一个应用程序(一种游戏帮助程序),它以特​​定的时间间隔向游戏发送击键(您可以指定将按下哪个键)。

问题是我需要以毫秒精度执行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%的准确度?

3 个答案:

答案 0 :(得分:1)

问题是你需要幸运,这一切都取决于.Tick到达的频率,根据你的硬件,这将是0.2-2毫秒左右。要避免这种情况非常困难,你可以尝试设置一个高进程优先级来窃取CPU以获得更多的Ticks。
这可以通过以下方式实现:

  System.Diagnostics.Process.GetCurrentProcess().PriorityClass =     ProcessPriorityClass.High;         

同时尝试设置

  while (sw.Elapsed.TotalMilliseconds <= SelectedLayout.JumpCollection[iJump + 1].WaitTime - dElapsed)

有时可以为你节省一些额外费用并提高准确度。

除了主要问题是Windows本身不是最好的计时器之外,DateTime.Now例如具有16ms的容差,并且从未被认为是“实时”操作系统。

作为旁注:如果你真的需要这个尽可能准确,我建议你研究一下Linux。

答案 1 :(得分:1)

使用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代码无法获得真实的实时代码。垃圾收集器可以介入,即使在循环周期之间,也可以开始收集对象,此时很有可能获得不精确的计时。

您可以通过更改垃圾收集器Latency Mode来减少发生这种情况的可能性,此时它不会在极低内存情况下执行大型收集。如果这对你来说还不够,可以考虑用一种语言编写上述解决方案,以便更好地保证时序(例如C ++)。

答案 2 :(得分:0)

您可以尝试使用ElapsedTicks。它是秒表可以测量的最小单位,您可以使用Frequency属性将经过的刻度数转换为秒(当然是几分之一秒)。我不知道它是否比Elapsed.TotalMilliseconds好,但值得一试。