Periodic data updates

I want to make a reservation right away that our code runs in a virtual environment (machine) of the .NET Framework which, in turn, runs on a general-purpose operating system, so we wonโ€™t talk about any accuracy even within 1-2 ms. Nevertheless, we will try to do everything in our power to increase the temporal accuracy.



Often in our program, it becomes necessary to update some information with a certain time interval. In my case, it was an update of snapshots (images) from ip cameras. Often, the business logic of an application sets certain limits on the frequency of data updates. For this time is 1 second.

The solution in the forehead is to install Thread.Sleep (1000) /Task.Await (1000) after the snapshot request.



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); }
      
      





But the term of our operation is a non-deterministic quantity. Therefore, an imitation of taking a snapshot looks something like this:



Run our program and run the output



 [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
      
      





As we see the lag will accumulate and therefore the business logic of our application will be violated.



 ,      60   1      49.
      
      





You can try to measure the average lag from and reduce sleep time by reducing the average deviation, but in this case we can get more requests than our business logic requires. We will never be able to predict, knowing that the request is completed up to 1 second - how many milliseconds we need to wait to ensure the necessary update period.



 ,      60   1     62.
      
      





The obvious solution suggests itself. Measure the time before and after the operation. And calculate their difference.



 while (true) { int sleepMs = 1000; var watch = Stopwatch.StartNew(); watch.Start(); Getsnapshot(); watch.Stop(); int needSleepMs = (int)(sleepMs - watch.ElapsedMilliseconds); Thread.Sleep(needSleepMs); }
      
      





Run our program now. If you are lucky you will see something like the following.



 [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
      
      





Why did I write with any luck? Because watch.Start () is executed before DoSomethink () and watch.Stop () after DoSomethink (); These operations are not instantaneous + the runtime environment itself does not guarantee the accuracy of the program execution time (x). Therefore, there will be overhead. Our DoSomethink () function runs from 0-1000 ms (y). Therefore, situations may arise when x + y> 1000 in such cases



  int needSleepMs = (int)(sleepMs - watch.ElapsedMilliseconds);
      
      





will take negative values โ€‹โ€‹and we will get an ArgumentOutOfRangeException since the Thread.Sleep () method should not take negative values.



In such cases, it makes sense to set the needSleepMs time to 0;

In fact, in reality, the DoSomethink () function can execute for as long as you want and we can get the variable overflow when casting to int. Then our sleep time

may exceed sleepMs;



You can fix this as follows:



 var needSleepMs = sleepMs - watch.ElapsedMilliseconds; if (needSleepMs > 0 && watch.ElapsedMilliseconds <= sleepMs) { needSleepMs = (int)needSleepMs; } else { needSleepMs = 0; } Thread.Sleep(needSleepMs);
      
      





In principle, everything is ready. But using this approach even in 1 place causes discomfort for the programmer's eye. And if there are dozens of such places in the program, then the code will turn into an unreadable heap ...



To fix this, we encapsulate our code in a function. Here you can put it in a separate class or use Global as a normal method for the class and use it as static (my version).



In our example, letโ€™s leave it for simplicity, leave it in the Program class



 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; }
      
      





Our input function accepts a link to the function to be executed and our planned waiting time. And it returns the time that our program should sleep.

For ease of use, we can also pass anonymous lambda functions to our function.



A complete listing of the program is given below:



 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; } } }
      
      






All Articles