Quiero hacer una reserva de inmediato para que nuestro código se ejecute en un entorno virtual (máquina) de .NET Framework, que a su vez se ejecuta en un sistema operativo de propósito general, por lo que no hablaremos de ninguna precisión incluso dentro de 1-2 ms. Sin embargo, intentaremos hacer todo lo que esté a nuestro alcance para aumentar la precisión temporal.
A menudo, en nuestro programa, es necesario actualizar cierta información con un cierto intervalo de tiempo. En mi caso, fue una actualización de instantáneas (imágenes) de cámaras IP. A menudo, la lógica empresarial de una aplicación establece ciertos límites en la frecuencia de las actualizaciones de datos. Para este tiempo es de 1 segundo.
La solución en la frente es instalar Thread.Sleep (1000) /Task.Await (1000) después de la solicitud de 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); }
Pero el término de nuestra operación es una cantidad no determinista. Por lo tanto, la simulación de tomar instantáneas se ve así:
Ejecute nuestro programa y ejecute la salida
[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, el retraso se acumulará y, por lo tanto, se violará la lógica comercial de nuestra aplicación.
, 60 1 49.
Puede intentar medir el retraso promedio y reducir el tiempo de sueño reduciendo la desviación promedio, pero en este caso podemos obtener más solicitudes de las que requiere nuestra lógica comercial. Nunca podremos predecir, sabiendo que la solicitud se completa hasta 1 segundo: cuántos milisegundos debemos esperar para garantizar el período de actualización necesario.
, 60 1 62.
La solución obvia se sugiere a sí misma. Mida el tiempo antes y después de la operación. Y calcular su diferencia.
while (true) { int sleepMs = 1000; var watch = Stopwatch.StartNew(); watch.Start(); Getsnapshot(); watch.Stop(); int needSleepMs = (int)(sleepMs - watch.ElapsedMilliseconds); Thread.Sleep(needSleepMs); }
Ejecute nuestro programa ahora. Si tienes suerte, verás algo como lo siguiente.
[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 qué escribí si tengo suerte? Porque watch.Start () se ejecuta antes de DoSomethink () y watch.Stop () después de DoSomethink (); Estas operaciones no son instantáneas + el entorno de tiempo de ejecución en sí mismo no garantiza la precisión del tiempo de ejecución del programa (x). Por lo tanto, habrá gastos generales. Nuestra función DoSomethink () se ejecuta de 0 a 1000 ms (y). Por lo tanto, pueden surgir situaciones cuando x + y> 1000 en tales casos
int needSleepMs = (int)(sleepMs - watch.ElapsedMilliseconds);
tomará valores negativos y obtendremos una ArgumentOutOfRangeException ya que el método Thread.Sleep () no debe tomar valores negativos.
En tales casos, tiene sentido establecer el tiempo needSleepMs en 0;
De hecho, en realidad, la función DoSomethink () puede ejecutarse durante el tiempo que desee y podemos obtener el desbordamiento de la variable al convertir a int. Entonces nuestro tiempo de sueño
puede exceder sleepMs;
Puede arreglar esto de la siguiente manera:
var needSleepMs = sleepMs - watch.ElapsedMilliseconds; if (needSleepMs > 0 && watch.ElapsedMilliseconds <= sleepMs) { needSleepMs = (int)needSleepMs; } else { needSleepMs = 0; } Thread.Sleep(needSleepMs);
En principio, todo está listo. Pero el uso de este enfoque incluso en 1 lugar causa incomodidad para el ojo del programador. Y si hay docenas de esos lugares en el programa, entonces el código se convertirá en un montón ilegible ...
Para solucionar esto, encapsulamos nuestro código en una función. Aquí puede ponerlo en una clase separada o usar Global como un volcado regular para la clase y usarlo como estático (mi versión).
En nuestro ejemplo, déjelo por simplicidad, déjelo en la clase Programa
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; }
Nuestra función de entrada acepta un enlace a la función a realizar y nuestro tiempo de espera planificado. Y devuelve el tiempo en que nuestro programa debería dormir.
Para facilitar su uso, también podemos pasar funciones lambda anónimas a nuestra función.
A continuación se incluye una lista completa del programa:
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; } } }