我想马上保留一下我们的代码在.NET Framework的虚拟环境(计算机)中运行,而该环境又在通用操作系统上运行,因此即使在1-2毫秒之内,我们也不会谈论任何准确性。 尽管如此,我们将尽力提高时间精度。
通常在我们的程序中,有必要以一定的时间间隔更新一些信息。 就我而言,这是来自IP摄像机的快照(图像)的更新。 通常,应用程序的业务逻辑对数据更新的频率设置某些限制。 这个时间是1秒。
前额中的解决方案是在快照请求之后安装Thread.Sleep(1000)/Task.Await(1000)。
static void Getsnapshot() { var rnd = new Random() var sleepMs = rnd.Next(0, 1000); Console.WriteLine($"[{DateTime.Now.ToString("mm:ss.ff")}] DoSomethink {sleepMs} ms"); Thread.Sleep(sleepMs); } while (true) { Getsnapshot(); Thread.Sleep(1000); }
但是我们的经营期限是不确定的。 因此,拍摄快照的模拟如下所示:
运行我们的程序并运行输出
[15:10.39] DoSomethink 974 ms [15:12.39] DoSomethink 383 ms [15:13.78] DoSomethink 99 ms [15:14.88] DoSomethink 454 ms [15:16.33] DoSomethink 315 ms [15:17.65] DoSomethink 498 ms [15:19.15] DoSomethink 708 ms [15:20.86] DoSomethink 64 ms [15:21.92] DoSomethink 776 ms [15:23.70] DoSomethink 762 ms [15:25.46] DoSomethink 123 ms [15:26.59] DoSomethink 36 ms [15:27.62] DoSomethink 650 ms [15:29.28] DoSomethink 510 ms [15:30.79] DoSomethink 257 ms [15:32.04] DoSomethink 602 ms [15:33.65] DoSomethink 542 ms [15:35.19] DoSomethink 286 ms [15:36.48] DoSomethink 673 ms [15:38.16] DoSomethink 749 ms
如我们所见,滞后将累积,因此将违反我们应用程序的业务逻辑。
, 60 1 49.
您可以尝试通过减少平均偏差来衡量平均延迟并减少睡眠时间,但是在这种情况下,我们可以获得比业务逻辑要求更多的请求。 知道请求将在1秒内完成,我们将永远无法预测-我们需要等待多少毫秒才能确保必要的更新时间。
, 60 1 62.
显而易见的解决方案表明了自己。 测量手术前后的时间。 并计算它们的差。
while (true) { int sleepMs = 1000; var watch = Stopwatch.StartNew(); watch.Start(); Getsnapshot(); watch.Stop(); int needSleepMs = (int)(sleepMs - watch.ElapsedMilliseconds); Thread.Sleep(needSleepMs); }
立即运行我们的程序。 如果幸运的话,您会看到类似以下的内容。
[16:57.25] DoSomethink 789 ms [16:58.05] Need sleep 192 ms [16:58.25] DoSomethink 436 ms [16:58.68] Need sleep 564 ms [16:59.25] DoSomethink 810 ms [17:00.06] Need sleep 190 ms [17:00.25] DoSomethink 302 ms [17:00.55] Need sleep 697 ms [17:01.25] DoSomethink 819 ms [17:02.07] Need sleep 181 ms [17:02.25] DoSomethink 872 ms [17:03.13] Need sleep 128 ms [17:03.25] DoSomethink 902 ms [17:04.16] Need sleep 98 ms [17:04.26] DoSomethink 717 ms [17:04.97] Need sleep 282 ms [17:05.26] DoSomethink 14 ms [17:05.27] Need sleep 985 ms
如果我很幸运,为什么还要写? 因为watch.Start()在DoSomethink()之前执行,而watch.Stop()在DoSomethink()之后执行; 这些操作不是瞬时的+运行时环境本身不能保证程序执行时间(x)的准确性。 因此,将有开销。 我们的DoSomethink()函数的运行时间为0-1000 ms(y)。 因此,当x + y> 1000时,可能会出现这种情况
int needSleepMs = (int)(sleepMs - watch.ElapsedMilliseconds);
将采用负值,并且由于Thread.Sleep()方法不应采用负值,因此我们将获得ArgumentOutOfRangeException。
在这种情况下,将needSleepMs时间设置为0是有意义的;
实际上,实际上,DoSomethink()函数可以执行任意长的时间,并且在转换为int时我们可以获得变量溢出。 那我们的睡眠时间
可能超过sleepMs;
您可以按以下方式解决此问题:
var needSleepMs = sleepMs - watch.ElapsedMilliseconds; if (needSleepMs > 0 && watch.ElapsedMilliseconds <= sleepMs) { needSleepMs = (int)needSleepMs; } else { needSleepMs = 0; } Thread.Sleep(needSleepMs);
原则上,一切就绪。 但是,即使在1个地方使用此方法也会引起程序员的不适。 如果程序中有许多这样的地方,那么代码将变成不可读的堆...
为了解决这个问题,我们将代码封装在一个函数中。 在这里,您可以将其放在单独的类中,或将Global用作该类的常规转储,并将其用作静态(我的版本)。
在我们的示例中,为简单起见,将其保留在Program类中
public static int NeedWaitMs(Action before, int sleepMs) { var watch = Stopwatch.StartNew(); watch.Start(); before(); watch.Stop(); var needSleepMs = sleepMs - watch.ElapsedMilliseconds; if (needSleepMs > 0 && watch.ElapsedMilliseconds <= sleepMs) return (int) needSleepMs; return 0; }
我们的输入功能接受到要执行的功能和计划的等待时间的链接。 它返回我们的程序应该休眠的时间。
为了易于使用,我们还可以将匿名lambda函数传递给我们的函数。
该程序的完整列表如下:
using System; using System.Diagnostics; using System.Threading; namespace ConsoleApp2 { class Program { static void Getsnapshot() { var rnd = new Random(); var sleepMs = rnd.Next(0, 1000); Console.WriteLine($"[{DateTime.Now.ToString("mm:ss.ff")}] DoSomethink {sleepMs} ms"); Thread.Sleep(sleepMs); } static void Main(string[] args) { while (true) { var sleepMs = NeedWaitMs(Getsnapshot, 1000); Console.WriteLine($"[{DateTime.Now.ToString("mm:ss.ff")}] Need sleep {sleepMs} ms {Environment.NewLine}"); Thread.Sleep(sleepMs); } } public static int NeedWaitMs(Action before, int sleepMs) { var watch = Stopwatch.StartNew(); before(); watch.Stop(); var needSleepMs = sleepMs - watch.ElapsedMilliseconds; return needSleepMs > 0 ? (int) needSleepMs : 0; } } }