【问题标题】:How to introduce an accurate small delay in a task without CPU overload? [duplicate]如何在没有 CPU 过载的情况下在任务中引入准确的小延迟? [复制]
【发布时间】:2017-05-07 11:17:41
【问题描述】:

我正在实施一种通信算法,以定期且非常快速地发送信息,即包之间的 1 毫秒。我有一个使用任务发送包的功能版本。这是我的代码示例:

private void Work()
{
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();

    while (!cancellationTokenSource.Token.IsCancellationRequested)
    {
        if (!Pack.PeriodicOn)
            cancellationTokenSource.Cancel();

        // Time used to send the packs before the interval time
        double tolerance = Pack.Interval * 0.2F;

        // In case of interval bigger than 25ms send pasks only 5ms before
        if (tolerance > 5) tolerance = 5;

        TimeSpan timeSpan = stopwatch.Elapsed;

        // The waiting time is controlled by the condition below, if the condition is false, the while loop continues execution         
        // Send the information a little bit before to interval time to deal with the transmision delay
        if (Pack.LastSent.TotalMilliseconds == 0 ||
             timeSpan.TotalMilliseconds - Pack.LastSent.TotalMilliseconds >=
             (Pack.Interval - tolerance))
        {
            SendData(Pack);
            Pack.LastSent = timeSpan;
        }
    }

    Pack.LastSent = new TimeSpan(0);
}

我的问题在于 CPU 使用率增加到不理想的水平。我知道我可以通过引入一些延迟来避免这种情况,但是, Thread.Sleep(1) 非常不准确,包之间的实际传输间隔增加,如果我使用 await Task.Delay(1) 似乎会产生相同的效果。

有没有人有替代方法来准确地引入任务延迟?

提前致谢!

【问题讨论】:

  • Windows 不是实时操作系统:/
  • 其他 SO 答案,例如stackoverflow.com/questions/6254703/… 似乎在说旋转 CPU,就像你正在做的那样,是要走的路。
  • 理解您所说的“非常不准确”会有所帮助。你看到多少变化?你系统的定时器间隔是多少(默认是15ms)
  • 您可以通过插入“Thread.Sleep(Pack.Interval-(timeSpan.TotalMilliseconds - Pack.LastSent.TotalMilliseconds))”作为 while 循环的最后一条指令来等待适当的睡眠延迟。跨度>
  • 我正在尝试尽可能地减小方差...很确定 Thread.Slep(x) 不会解决问题,即使 x 大于 15 它也无法完成延迟差异很小,这就是我要问的原因:)

标签: c# .net multithreading winforms task


【解决方案1】:

[在 Windows 上] 如何在没有 CPU 过载的情况下引入准确的小 [1ms] 延迟?

你不能,对不起。 Windows 上的系统调度程序只能稍微调整(通过在 Windows Server 的高级系统属性对话框中选择Adjust for best performance of Applicationssetting a registry value),但它不会进入亚毫秒范围。如果这样做,整个系统的性能将受到无法接受的影响。

根据您的硬件,我认为可能可以将系统时钟分辨率降低至 0.5 毫秒;但是,您可以设置的最小线程量是 6,这需要两个时钟滴答来减少到 0。所以您仍然会得到 1ms 的量,这至少是您需要的两倍慢。而且,当然,您的电池寿命会减少约 15%(据我所知)。

欲了解更多信息,请阅读Windows Internals

【讨论】:

    【解决方案2】:

    Windows 不是实时操作系统,因此不能保证计时器准确工作。典型系统时钟的精度约为 15 ms。但是,可以获得比标准 System.Threading.Timer 更准确的事件。 Windows API 具有专为多媒体场景设计的计时器,可以以更精确的间隔触发。我已经更新了我维护的 GitHub 存储库 HighPrecisionTimer 中的代码,该代码利用了该 API,以包含基于任务的 MultimediaTimer.Delay 方法:

    private static async Task RunEveryMillisecond(CancellationToken token)
    {
        Stopwatch s = Stopwatch.StartNew();
        TimeSpan prevValue = TimeSpan.Zero;
        int i = 0;
        while (true)
        {
            Console.WriteLine(s.ElapsedMilliseconds);
            await MultimediaTimer.Delay(1, token);
            if (Console.KeyAvailable)
            {
                return;
            }
    
            i++;
        }
    }
    

    请注意,这在我的系统上大约每 1.5 毫秒触发一次,并且在运行时它仍然可以占用大约 10% 的 CPU,因此它对系统资源的影响不容忽视。项目中包含的标准计时器方法在 1 ms 级别上运行方法更准确和更有效(更少的 CPU 开销,~1%)。我的猜测是基于任务的延迟方法中存在更多的分配和垃圾收集开销。

    请注意,使用此 API 可能会产生副作用,例如缩短电池寿命。然而,它对于需要更短时间的测试类型场景很有用。

    【讨论】:

    • 非常感谢!引入的 CPU 开销(运行通信的整个应用程序约为 20%)与我之前的开销(约 80% 或更高)相比,我可以处理那。在电池寿命的情况下,我必须测试性能,但在我看来我不会有问题,因为通信周期不会太长,我会在空闲时停止计时器,谢谢! ;)
    【解决方案3】:

    您可以在 1 毫秒延迟后启动多个连续线程,例如 n(n=10 或 20 等)(n 是线程数),并且在每个任务中您可以等待 n 毫秒。

    【讨论】:

      猜你喜欢
      • 2012-07-03
      • 2019-09-26
      • 2019-05-14
      • 1970-01-01
      • 2020-02-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多