Saltar al contenido

No entiendo lo que está haciendo Application.ProcessMessages en Delphi

Anduvimos indagando en todo el mundo on line para brindarte la respuesta a tu problema, si continúas con alguna inquietud déjanos la pregunta y contestaremos porque estamos para ayudarte.

Solución:

No hay una forma corta de responder correctamente a esta pregunta.

El medio principal por el cual las aplicaciones de Windows interactúan con el sistema operativo es a través de un sistema de mensajería. Todo lo que sucede en una aplicación de Windows sucede en respuesta a un mensaje.

Por ejemplo :

Si hace clic en la pantalla, el sistema operativo decide en qué aplicación se hizo clic y publica un mensaje en esa aplicación que indica que ha recibido un clic (junto con la ubicación de ese clic).

Si una ventana se mueve y revela una parte de su aplicación debajo de ella, el sistema operativo envía un mensaje que le dice a su aplicación que se vuelva a pintar.

La lista continua. Todo lo que sucede está impulsado por mensajes.

Ahora, cada aplicación tiene un subproceso de interfaz de usuario principal (el subproceso “Principal”) y este subproceso tiene una función principal: se ejecuta en un bucle infinito que verifica estos mensajes del sistema operativo y luego ejecuta el código necesario en respuesta a esos mensajes.

El problema

Llegas y comienzas a escribir una solicitud. Puede escribir un código como este:

procedure TForm1.Button1Click(Sender: TObject);
var i : Integer;
begin
  for i := 0 to 99999999999 do begin
    SolveTheProblemsOfTheWorld(i);
    CalculatePiToABillionPlaces;
  end;
end;

En la estructura más grande del programa, la ejecución en el hilo principal se ve así:

  1. Buscar mensajes
  2. Para cada mensaje: ejecute los controladores asociados
  3. Volver a Verificar mensajes (bucle)

Así que este bucle avanza felizmente cuando, de repente, uno de los controladores asociados (Button1Click arriba) comienza a tardar mucho en completarse. La clave para entender es que un controlador de mensajes debe completarse antes de que se pueda ejecutar el siguiente. Si hace clic en una barra de desplazamiento, por ejemplo, y la arrastra, pero ha adjuntado un controlador a OnClick de la barra de desplazamiento que tarda 10 segundos en completarse, la aplicación no verá la operación de arrastre hasta que se complete el controlador de clic. Mientras tanto, la cola de mensajes se está llenando y el hilo principal no hace nada al respecto.

Seguramente ha experimentado esto: de repente, su aplicación no responde a los clics. No puede interactuar con él, no puede mover la ventana, si arrastra otra ventana por encima de ella, la aplicación ni siquiera se volverá a pintar, simplemente se llena con la basura que dejó encima.

Ingrese ProcessMessages

La solución perezosa y terrible para no poner su código de larga ejecución en un hilo

¿Qué estás haciendo cuando llamas? Application.ProcessMessages es, en medio de uno de estos controladores, instruir al hilo principal que se tome un descanso para volver a revisar la cola de mensajes y vaciar cualquier mensaje que se haya estado acumulando; para manejar clics, movimientos de ventana, entradas, pulsaciones de teclas, repintarse si es necesario, etc.

procedure TForm1.Button1Click(Sender: TObject);
var i : Integer;
begin
  for i := 0 to 99999999999 do begin
    SolveTheProblemsOfTheWorld(i);
    CalculatePiToABillionPlaces;
    Application.ProcessMessages;
  end;
end;

Esto puede parecer superficialmente como algo sensato, ya que le permite mantener la respuesta de la aplicación durante un ciclo de larga duración. Sin embargo, en última instancia, este estilo de programación se considera una práctica extremadamente deficiente por un gran número de muy buenas razones. Basta decir que en cualquier lugar que tenga la tentación de usar Application.ProcessMessages es un caso sólido para mover ese trabajo a un hilo de fondo.

Para obtener más detalles, echemos un vistazo al código real:

procedure TApplication.ProcessMessages;
var
  Msg: TMsg;
begin
  while ProcessMessage(Msg) do loop;
end;

Entonces, cuando haces esta llamada a Application.ProcessMessages está ejecutando un ciclo que es, uno por uno, vaciando mensajes de la cola de mensajes (y ejecutando todo el código que está adjunto a los controladores que están reaccionando a esos mensajes) hasta que esté vacío. Cuando esté vacío y no haya más mensajes para procesar, el control volverá a la siguiente línea de su programa.

El punto importante es que la interacción fluida y fluida que experimenta al usar un programa es una ilusión completa que depende exactamente de que este bucle de mensajes se procese lo más rápido posible. Cuanto menor sea la demora de tiempo entre la publicación de un mensaje en su aplicación y el mensaje que se maneja, más se sentirá su aplicación como si estuviera viva y respondiera.

Es por esta razón que todo el código que se adjunta a los controladores de la interfaz de usuario debe ejecutarse rápidamente. Las operaciones de larga ejecución deben interrumpirse para que el manejo de mensajes pueda continuar (es decir: Application.ProcessMessages) o esas operaciones deben moverse a un subproceso separado donde se pueden ejecutar sin atar el subproceso principal y alejarlo de su responsabilidad principal (que es mantener viva la interfaz de usuario).


Para obtener un artículo realmente bueno sobre el tema, consulte:

La odisea de una llave por Peter Below

(Enlace de archivo de Internet … en caso de que el anterior muera)

Resumen: Este artículo sigue la ruta de un mensaje de pulsación de tecla a través de la VCL. Aprenderás cómo key se implementa el procesamiento, cómo funcionan los eventos OnKey y qué puntos de intervención para el programador se pueden encontrar en todo el proceso. Además, se explican cosas como el procesamiento de mensajes y aprenderá a rastrear mensajes en el depurador desde el bucle de mensajes hasta su destino final.

Mensajería

Una aplicación Delphi VCL es una aplicación de Windows y mucha de la comunicación entre los componentes de Windows (como formularios, cuadros de edición, pero también cosas ocultas como temporizadores) se realiza usando cosas llamadas mensajes.

Estos mensajes se envían a una cola especial. Puede enviar dichos mensajes directamente (utilizando el SendMessage y PostMessage Funciones API), pero los mensajes también se envían indirectamente cuando establece una propiedad, como el Text de un TEdit.

Estos mensajes se utilizan para notificar a los controles que importantes eventos han ocurrido dentro del programa para que puedan responder adecuadamente. Ese concepto de enviar y responder mensajes es el principio central de un paradigma conocido como programación impulsada por eventos.

Muchos sistemas operativos modernos se basan en eventos, como Windows. De hecho, Delphi VCL incluye una gran cantidad de funciones de Windows, y muchas de las cosas que haces darán como resultado que se envíen mensajes entre los controles, que notifican a esos controles los clics del mouse, las pulsaciones del teclado, los cambios en la configuración de Windows, el cierre de la aplicación, el movimiento del control, etc.

Mainthread no procesa mensajes al ejecutar código

Los sistemas operativos también permiten que los programas tengan cosas llamadas hilos. Los subprocesos son como pequeñas piezas de un programa que parecen funcionar simultáneamente (y en algunos casos realmente hacer ejecutar simultáneamente). Una aplicación puede tener más de un hilo, pero siempre está el hilo principal, que también es el hilo responsable de aceptar mensajes entrantes y actualizar la GUI.

Cuando el hilo principal de la aplicación está inactivo (sin ejecutar código), verificará la cola para ver si hay mensajes y los procesará. Mientras se ejecuta otro código no relacionado, este no poder ocurrir. Considere este ejemplo:

Label1.Caption := 'A';
Sleep(5000); // Sleep is simulating a long, blocking process.
Label1.Caption := 'B';

Si ejecuta este código, esperaría que la etiqueta obtenga un título de ‘A’, que cambia a ‘B’ cinco segundos después. Pero esto no es true. Establecer el título de una etiqueta desencadena un repintado del control a través de un mensaje. Debido a que el hilo principal aún está bloqueado por este código (incluso por el comando de suspensión), el mensaje aún no se procesa.

Solo cuando han pasado los 5 segundos y el título se establece en ‘B’, el hilo principal queda inactivo y ejecutará los repintados que activamos al configurar el título. También tenga en cuenta que otras interacciones, como hacer clic en un botón o arrastrar la ventana, se posponen hasta que hayan pasado los 5 segundos. Toda la interfaz de usuario se congela mientras el hilo principal ejecuta ese código.

Application.ProcessMessages al rescate

Application.ProcessMessages simplemente forzará a la aplicación a vaciar su cola de mensajes, por lo que puede ‘arreglar’ el código de esta manera:

Label1.Caption := 'A';
Application.ProcessMessages;
Sleep(5000);
Label1.Caption := 'B';

Por lo general, no habrá solo una suspensión en su código, sino mucho procesamiento real. Mediante el uso Application.ProcessMessage Con frecuencia, puede mantener la interfaz de la aplicación respondiendo, incluso cuando está ejecutando código.

Pero esto es un poco sucio, el hilo principal (y la interfaz) todavía estará bloqueado, a menos que logre apretar un lote de llamadas a Application.ProcessMessages. Y además de procesar mensajes de pintura, también se procesan otros mensajes, por lo que la aplicación podría procesar un evento de clic justo en medio de su proceso.

Entonces, los documentos que leíste son correctos: Application.ProcessMessages vaciará el cola de mensajes. Su supervisor no tiene exactamente la razón. No asigna tiempo de procesamiento adicional por decir, sino que simplemente integra el vaciado de la cola de mensajes en el código que se ejecuta, mientras que normalmente los mensajes permanecerían en la cola hasta que la aplicación quede inactiva.

Internos

Internamente, la propia aplicación hace lo mismo todo el tiempo. Su procedimiento principal, Application.Run, tiene este aspecto (pseudocódigo simplificado):

  repeat

    ProcessMessageFromTheQueue;
    If QueueIsEmpty then
      TellWindowsToWakeMeUpWhenANewMessageArrives;   

  until Terminated;

Es el procesamiento el que ejecuta el código, por ejemplo, cuando un WM_MOUSECLICK se procesa el mensaje, se activará (a través de un poco de magia Delphi VCL), su Button1Click controlador de eventos. Dado que se trata de un solo hilo, todo se ejecuta secuencialmente, por lo que comprenderá que ProcessMessageFromTheQueue solo regresa cuando el controlador de eventos finaliza. Delphi maneja solo un mensaje a la vez, y solo hace el siguiente cuando se termina el procesamiento del anterior.

¿O lo hace?

Application.ProcessMessages Se ve como esto:

  repeat

    ProcessMessageFromTheQueue;

  until QueueIsEmpty;

Por lo tanto, hace casi lo mismo que el bucle principal de la aplicación: toma un mensaje de la cola y lo procesa. Esto significa que, de hecho, puede procesar los siguientes mensajes mientras se encuentra dentro del procesamiento del último. Un truco conveniente, pero que puede complicar fácilmente su aplicación y causar todo tipo de efectos secundarios no deseados.

La forma correcta: hilos

La mayoría de la gente considera que es una mejor solución usar un hilo separado para ejecutar su código y simplemente enviar señales al hilo principal cuando necesita actualizar una etiqueta o una barra de progreso. De esa manera, el hilo principal está inactivo todo el tiempo y seguirá respondiendo a los clics del mouse, etc.

Sin embargo, enhebrar es difícil para un principiante. Algunas cosas para tener en mente:

  • Un hilo (que no sea el hilo principal) no debe actualizar directamente la GUI (por ejemplo, un hilo no puede establecer Label1.Caption. podría fallar.
  • Las variables y otros recursos no deben ser modificados por varios subprocesos al mismo tiempo, por lo que debe tomar precauciones para evitarlo (secciones críticas).
  • Debe (en la mayoría de los casos) evitar que un usuario ejecute la misma acción que ya se está ejecutando.
  • En muchos casos, debe encontrar una forma adecuada de interrumpir un hilo en ejecución si un usuario desea cancelar el proceso o cerrar la aplicación.

De todos modos, esta respuesta no debería ser un curso completo sobre enhebrado, pero ahora al menos sabes qué Application.ProcessMessages es para. También tenga en cuenta que muchas personas lo consideran ‘malvado’, pero como principiante, puede probarlo de todos modos para descubrir sus fortalezas y debilidades por sí mismo. Si lo hace, espero que esta información al menos lo inspire a pasar a los hilos tan pronto como tenga la experiencia suficiente para el próximo capítulo.

valoraciones y comentarios

Más adelante puedes encontrar las crónicas de otros desarrolladores, tú aún eres capaz dejar el tuyo si dominas el tema.

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


Tags : /

Utiliza Nuestro Buscador

Deja una respuesta

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