【问题标题】:C# Process wait millisecond preciseC#进程等待毫秒精确
【发布时间】:2015-09-13 12:34:48
【问题描述】:

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

问题是我需要以毫秒精度执行 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。
  • 还可以尝试设置while (sw.Elapsed.TotalMilliseconds &lt;= SelectedLayout.JumpCollection[iJump + 1].WaitTime - dElapsed),这有时会为您节省另一个时间并提高一点准确性
  • *another comment (sry for overposting):设置优先级(如前所述):System.Diagnostics.Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
  • @Johan 为什么不发布这些 cmets 作为答案?
  • @user1666620 好的,会的:)

标签: c# .net timer precision


【解决方案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 具有 16 毫秒的容差,并且从未被认为是“实时”操作系统。

附带说明:如果您确实需要尽可能准确,我建议您研究 Linux。

【讨论】:

    【解决方案2】:

    使用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++)编写上述解决方案。

    【讨论】:

      【解决方案3】:

      您可以尝试使用 ElapsedTicks。它是秒表可以测量的最小单位,您可以使用Frequency 属性将经过的滴答数转换为秒(当然还有秒的小数部分)。不知道比Elapsed.TotalMilliseconds好不值得一试。

      【讨论】:

      • 我认为 IsHighFrequency 是真的?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-11
      • 2021-02-26
      • 2015-05-02
      • 1970-01-01
      • 2020-03-01
      • 1970-01-01
      相关资源
      最近更新 更多