Atualizações periódicas de dados

Quero fazer uma reserva imediata de que nosso código é executado em um ambiente virtual (máquina) do .NET Framework, que por sua vez é executado em um sistema operacional de uso geral, portanto, não falaremos sobre precisão, mesmo em 1-2 ms. No entanto, tentaremos fazer tudo ao nosso alcance para aumentar a precisão temporal.

Freqüentemente, em nosso programa, torna-se necessário atualizar algumas informações com um determinado intervalo de tempo. No meu caso, foi uma atualização de snapshots (imagens) de câmeras IP. Freqüentemente, a lógica comercial de um aplicativo define certos limites na frequência das atualizações de dados. Por esse tempo é de 1 segundo.
A solução na testa é instalar o Thread.Sleep (1000) /Task.Await (1000) após a solicitação de captura instantânea.

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

Mas o termo de nossa operação é uma quantidade não determinística. Portanto, a simulação de tirar instantâneos se parece com isso:

Execute nosso programa e execute a saída

 [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 

Como vemos, o atraso se acumulará e, portanto, a lógica comercial de nosso aplicativo será violada.

 ,      60   1      49. 

Você pode tentar medir o atraso médio e reduzir o tempo de suspensão reduzindo o desvio médio, mas, neste caso, podemos receber mais solicitações do que a lógica de negócios exige. Nunca conseguiremos prever, sabendo que a solicitação é concluída em até 1 segundo - quantos milissegundos precisamos esperar para garantir o período de atualização necessário.

 ,      60   1     62. 

A solução óbvia se sugere. Meça o tempo antes e depois da operação. E calcule a diferença deles.

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

Execute nosso programa agora. Se você tiver sorte, verá algo como o seguinte.

 [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 

Por que eu escrevi se tenho sorte? Porque watch.Start () é executado antes de DoSomethink () e watch.Stop () após DoSomethink (); Essas operações não são instantâneas + o próprio ambiente de tempo de execução não garante a precisão do tempo de execução do programa (x). Portanto, haverá sobrecarga. Nossa função DoSomethink () é executada de 0 a 1000 ms (y). Portanto, situações podem surgir quando x + y> 1000 nesses casos

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

receberá valores negativos e obteremos um ArgumentOutOfRangeException, pois o método Thread.Sleep () não deve aceitar valores negativos.

Nesses casos, faz sentido definir o tempo needSleepMs como 0;
De fato, na realidade, a função DoSomethink () pode ser executada pelo tempo que você quiser e podemos obter o estouro da variável ao converter para int. Então nossa hora de dormir
pode exceder sleepMs;

Você pode corrigir isso da seguinte maneira:

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

Em princípio, tudo está pronto. Mas usar essa abordagem mesmo em um local causa desconforto para os olhos do programador. E se houver dezenas desses lugares no programa, o código se transformará em um monte ilegível ...

Para consertar isso, encapsulamos nosso código em uma função. Aqui você pode colocá-lo em uma classe separada ou usar Global como um despejo regular para a classe e usá-lo como estático (minha versão).

No nosso exemplo, vamos deixar por simplicidade, na classe 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; } 

Nossa função de entrada aceita um link para a função a ser executada e nosso tempo de espera planejado. E retorna o tempo que nosso programa deve dormir.
Para facilitar o uso, também podemos passar funções lambda anônimas para nossa função.

Uma lista completa do programa é fornecida abaixo:

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

Source: https://habr.com/ru/post/pt474038/


All Articles