Saltar al contenido

¿Cuál es la mejor manera de programar un retraso en Delphi?

Solución:

Respondiendo sus preguntas por turno:

  1. ¿Es esta una forma adecuada de programar un retraso?

Sí (pero también “no” – ver más abajo).

La “forma adecuada” varía según los requisitos específicos y el problema que se resuelve. No hay Verdad universal sobre esto y cualquiera que le diga lo contrario está tratando de venderle algo (parafraseando).

En algunos casos, esperar un evento es el mecanismo de demora adecuado. En otros casos no.

  1. Si la respuesta es sí, ¿por qué es mejor que una llamada a dormir ()?

Ver arriba: la respuesta es si. Sin embargo, esta segunda pregunta simplemente no tiene sentido, ya que asume que Dormir() es siempre y por necesidad nunca de la manera correcta que, como se explica en la respuesta al # 1 anterior, no es necesariamente el caso.

Dormir() Puede que no sea la mejor o más apropiada forma de programar un retraso en todos escenarios, pero hay escenarios en los que es más práctico y no presenta inconvenientes importantes.

Por qué la gente evita dormir () ing

Dormir() es un problema potencial precisamente porque se trata de un retraso incondicional que no se puede interrumpir hasta que haya transcurrido un período de tiempo específico. Los mecanismos de demora alternativos típicamente logran exactamente lo mismo con la única diferencia de que existe algún mecanismo alternativo para reanudar la ejecución, que no sea el mero paso del tiempo.

La espera de un evento se retrasa hasta que ocurre (o se destruye) o ha pasado un período de tiempo específico.

Esperar un mutex provoca un retraso hasta que se adquiere (o se destruye) el mutex o ha pasado un período de tiempo específico.

etc.

En otras palabras: mientras que algunos mecanismos de retardo son interrumpibles. Dormir() no es. Pero si se equivocan los otros mecanismos, todavía existe la posibilidad de introducir problemas importantes y, a menudo, de una manera que puede ser mucho más difícil de identificar.

Problemas con Event.WaitFor () en este caso

El prototipo en la pregunta destaca un problema potencial de usar alguna mecanismo que suspende la ejecución de su código si el resto de ese código no se implementa de una manera que sea compatible con ese enfoque en particular:

 Form1.Timer1.Enabled := true;
 Form1.EventManager.ResetEvent;
 Form1.EventManager.WaitFor(INFINITE);

Si este código se ejecuta en el hilo principal, entonces Temporizador1 nunca sucederá.

El prototipo en la pregunta ejecuta esto en un hilo, por lo que este problema en particular no surge, pero vale la pena explorar el potencial ya que el prototipo introduce un problema diferente como resultado de la participación de este hilo.

Al especificar un INFINITO espera el tiempo de espera en tu WaitFor () en el evento, suspendes la ejecución del hilo hasta que ocurra ese evento. los TTimer El componente utiliza el mecanismo de temporizador basado en mensajes de Windows, en el que un WM_TIMER El mensaje se envía a su cola de mensajes cuando el temporizador ha transcurrido. Para el WM_TIMER mensaje para que ocurra, su aplicación debe estar procesando su cola de mensajes.

También se pueden crear temporizadores de Windows que proporcionarán una devolución de llamada en otro hilo, lo que podría ser un enfoque más apropiado en este caso (ciertamente artificial). Sin embargo, esta no es una capacidad ofrecida por VCL. TTimer componente (a partir de XE4 al menos, y observo que está utilizando XE2).

Problema # 1

Como se señaló anteriormente, WM_TIMER los mensajes dependen de que su aplicación procese su cola de mensajes. Ha especificado un temporizador de 2 segundos, pero si el proceso de su solicitud está ocupado haciendo otro trabajo, podría tardar mucho más de 2 segundos en procesarse ese mensaje.

Vale la pena mencionar aquí que Dormir() también está sujeto a cierta inexactitud: garantiza que un hilo se suspenda durante por lo menos el período de tiempo especificado, no garantizar exactamente el retraso especificado.

Problema # 2

El prototipo ideó un mecanismo para retrasar 2 segundos usando un temporizador y un evento para lograr casi exactamente el mismo resultado que podría haberse logrado con una simple llamada a Dormir().

La única diferencia entre esto y un simple Dormir() La llamada es que su hilo también se reanudará si se destruye el evento que está esperando.

Sin embargo, en una situación del mundo real en la que un procesamiento adicional sigue al retraso, esto es en sí mismo un problema potencialmente significativo si no se maneja correctamente. En el prototipo no se atiende en absoluto esta eventualidad. Incluso en este caso simple, lo más probable es que si el evento ha sido destruido, también lo ha hecho el Temporizador1 que el hilo intenta deshabilitar. Un Infracción de acceso es probable que ocurra en el hilo como resultado cuando intenta deshabilitar ese temporizador.

Desarrollador de advertencias

Evitar dogmáticamente el uso de Dormir() no sustituye a la correcta comprensión de todos los mecanismos de sincronización de subprocesos (de los cuales los retrasos son solo uno) y la forma en que funciona el propio sistema operativo, para que la técnica correcta se pueda desplegar según lo requiera cada ocasión.

De hecho, en el caso de su prototipo, Dormir() proporciona posiblemente la “mejor” solución (si la confiabilidad es la key métrica) ya que la simplicidad de esa técnica asegura que su código se reanudará después de 2 segundos sin caer en las trampas que esperan a los incautos con técnicas demasiado complicadas (con respecto al problema en cuestión).

Dicho esto, este prototipo es claramente un ejemplo artificial.

En mi experiencia hay muy pocos situaciones prácticas donde Dormir() es la solución óptima, aunque a menudo es la más simple y menos propensa a errores. Pero yo nunca diría nunca.

Escenario: desea realizar algunas acciones consecutivas con un cierto retraso entre ellas.

¿Es esta una forma adecuada de programar un retraso?

Yo diría que hay mejores formas, ver más abajo.

Si la respuesta es sí, ¿por qué es mejor que una llamada a dormir ()?

Dormir en el hilo principal es una mala idea: recuerde, el paradigma de Windows está impulsado por eventos, es decir, realice su tarea en función de una acción y luego deje que el sistema se encargue de lo que sucede a continuación. Dormir en un hilo también es malo, ya que puede detener mensajes importantes del sistema (en caso de apagado, etc.).

Tus opciones son:

  • Maneje sus acciones desde un temporizador en el hilo principal como una máquina de estado. Mantenga un registro del estado y simplemente ejecute la acción que representa este estado en particular cuando se activa el evento del temporizador. Esto funciona para el código que finaliza en poco tiempo para cada evento del temporizador.

  • Pon la línea de acciones en un hilo. Utilice un tiempo de espera de evento como temporizador para evitar congelar el hilo con llamadas de suspensión. A menudo, este tipo de acciones están vinculadas a E / S, donde se llaman funciones con tiempo de espera incorporado. En esos casos, el número de tiempo de espera sirve como un retraso natural. Así es como se construyen todas mis bibliotecas de comunicación.

Ejemplo de la última alternativa:

procedure StartActions(const ShutdownEvent: TSimpleEvent);
begin
  TThread.CreateAnonymousThread(
    procedure
    var
      waitResult: TWaitResult;
      i: Integer;
    begin
      i := 0;
      repeat
        if not Assigned(ShutdownEvent) then
          break;
        waitResult := ShutdownEvent.WaitFor(2000);
        if (waitResult = wrTimeOut) then
        begin
          // Do your stuff
          // case i of
          //   0: ;
          //   1: ;
          // end;
          Inc(i);
          if (i = 10) then
            break;
        end
        else 
          break;  // Abort actions if process shutdown
      until Application.Terminated;
    end
  ).Start;
end;

Llámalo:

var
  SE: TSimpleEvent;
...
SE := TSimpleEvent.Create(Nil,False,False,'');
StartActions(SE);

Y para abortar las acciones (en caso de cierre del programa o aborto manual):

SE.SetEvent;
...
FreeAndNil(SE);

Esto creará un hilo anónimo, donde la sincronización es impulsada por un TSimpleEvent. Cuando la línea de acciones esté lista, el hilo se autodestruirá. El objeto de evento “global” se puede utilizar para cancelar las acciones manualmente o durante el cierre del programa.

Ten en cuenta mostrar este escrito si te valió la pena.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *