Uso de Application.DoEvents ()

272

¿Se Application.DoEvents()puede usar en C #?

¿Es esta función una forma de permitir que la GUI se ponga al día con el resto de la aplicación, de la misma manera que lo hace VB6 DoEvents?

Craig Johnston
fuente
35
DoEventses parte de Windows Forms, no el lenguaje C #. Como tal, se puede usar desde cualquier lenguaje .NET. Sin embargo, no debe usarse desde ningún lenguaje .NET.
Stephen Cleary
3
Es 2017. Use Async / Await. Vea el final de la respuesta @hansPassant para más detalles.
FastAl

Respuestas:

468

Hmya, la mística perdurable de DoEvents (). Ha habido una enorme reacción en contra, pero nadie explica realmente por qué es "malo". El mismo tipo de sabiduría que "no mutes una estructura". Erm, ¿por qué el tiempo de ejecución y el lenguaje admiten la mutación de una estructura si eso es tan malo? La misma razón: te disparas en el pie si no lo haces bien. Fácilmente. Y hacerlo bien requiere saber exactamente lo que hace, lo que en el caso de DoEvents () definitivamente no es fácil de asimilar.

De buenas a primeras: casi cualquier programa de formularios Windows Forms contiene una llamada a DoEvents (). Está inteligentemente disfrazado, sin embargo, con un nombre diferente: ShowDialog (). Es DoEvents () que permite que un diálogo sea modal sin que congele el resto de las ventanas de la aplicación.

La mayoría de los programadores quieren usar DoEvents para evitar que su interfaz de usuario se congele cuando escriben su propio bucle modal. Ciertamente hace eso; envía mensajes de Windows y recibe cualquier solicitud de pintura. Sin embargo, el problema es que no es selectivo. No solo envía mensajes de pintura, sino que también entrega todo lo demás.

Y hay un conjunto de notificaciones que causan problemas. Vienen de unos 3 pies delante del monitor. El usuario podría, por ejemplo, cerrar la ventana principal mientras se ejecuta el bucle que llama a DoEvents (). Eso funciona, la interfaz de usuario se ha ido. Pero su código no se detuvo, todavía está ejecutando el bucle. Eso es malo. Muy muy mal.

Hay más: el usuario puede hacer clic en el mismo elemento de menú o botón que hace que comience el mismo ciclo. Ahora tiene dos bucles anidados que ejecutan DoEvents (), el bucle anterior se suspende y el nuevo bucle comienza desde cero. Eso podría funcionar, pero las probabilidades son escasas. Especialmente cuando finaliza el bucle anidado y se reanuda el suspendido, tratando de terminar un trabajo que ya se completó. Si eso no bombardea con una excepción, entonces seguramente los datos están revueltos.

Volver a ShowDialog (). Ejecuta DoEvents (), pero tenga en cuenta que hace algo más. Se desactiva todas las ventanas de la aplicación , aparte del diálogo. Ahora que se resuelve el problema de 3 pies, el usuario no puede hacer nada para estropear la lógica. Se resuelven los modos de falla de cerrar la ventana y comenzar el trabajo nuevamente. O para decirlo de otra manera, no hay forma de que el usuario haga que su programa ejecute el código en un orden diferente. Se ejecutará de manera predecible, tal como lo hizo cuando probó su código. Hace que los diálogos sean extremadamente molestos; ¿Quién no odia tener un diálogo activo y no poder copiar y pegar algo desde otra ventana? Pero ese es el precio.

Que es lo que se necesita para usar DoEvents de forma segura en su código. Establecer la propiedad Enabled de todos sus formularios en false es una forma rápida y eficiente de evitar problemas. Por supuesto, a ningún programador le gusta hacer esto. Y no lo hace. Es por eso que no deberías usar DoEvents (). Deberías usar hilos. A pesar de que te entregan un arsenal completo de formas de disparar a tu pie de formas coloridas e inescrutables. Pero con la ventaja de que solo disparas tu propio pie; no dejará (típicamente) que el usuario le dispare a ella.

Las próximas versiones de C # y VB.NET proporcionarán un arma diferente con las nuevas palabras clave de espera y asíncronas. Inspirado en pequeña parte por los problemas causados ​​por DoEvents y subprocesos, pero en gran parte por el diseño de API de WinRT que requiere que mantengas actualizada tu interfaz de usuario mientras se realiza una operación asincrónica. Como leer de un archivo.

Hans Passant
fuente
1
Sin embargo, esta es solo la punta del iceberg. He estado usando Application.DoEventssin problemas hasta que agregué algunas características UDP a mi código, lo que resultó en el problema descrito aquí . Me gustaría saber si hay alguna forma de evitar que con DoEvents.
Darda
esta es una buena respuesta, lo único que siento que se pierde es una explicación / discusión sobre la ejecución y el diseño seguro para subprocesos o división de tiempo en general, pero eso es fácilmente tres veces más texto nuevamente. :)
jheriko
55
Se beneficiaría de una solución más práctica que generalmente "usar hilos". Por ejemplo, el BackgroundWorkercomponente gestiona subprocesos por usted, evitando la mayoría de los resultados coloridos del tiro al pie, y no requiere versiones de lenguaje C # de última generación.
Ben Voigt
29

Puede ser, pero es un truco.

¿ Ver es DoEvents Evil? .

Directo desde la página de MSDN a la que el dispositivo hace referencia:

Al llamar a este método, se suspende el hilo actual mientras se procesan todos los mensajes de la ventana de espera. Si un mensaje hace que se desencadene un evento, se pueden ejecutar otras áreas del código de su aplicación. Esto puede hacer que su aplicación muestre comportamientos inesperados que son difíciles de depurar. Si realiza operaciones o cálculos que llevan mucho tiempo, a menudo es preferible realizar esas operaciones en un nuevo hilo. Para obtener más información acerca de la programación asincrónica, consulte Descripción general de la programación asincrónica.

Entonces, Microsoft advierte contra su uso.

Además, lo considero un truco porque su comportamiento es impredecible y propenso a los efectos secundarios (esto proviene de la experiencia al tratar de usar DoEvents en lugar de girar un nuevo hilo o usar un trabajador en segundo plano).

Aquí no hay machismo: si funcionara como una solución robusta, estaría por todas partes. Sin embargo, intentar usar DoEvents en .NET no me ha causado más que dolor.

RQDQ
fuente
1
Vale la pena señalar que esa publicación es de 2004, anterior a .NET 2.0 y BackgroundWorkerayudó a simplificar la forma "correcta".
Justin
Convenido. Además, la biblioteca de tareas en .NET 4.0 también es bastante agradable.
RQDQ
1
¿Por qué sería un truco si Microsoft ha proporcionado una función para el propósito para el que a menudo se necesita?
Craig Johnston
1
@Craig Johnston: actualicé mi respuesta con más detalle por qué creo que DoEvents entra en la categoría de piratería informática.
RQDQ
Ese artículo de codificación de terror se refería a él como "DoEvents spackle". ¡Brillante!
gonzobrains 02 de
24

Sí, hay un método DoEvents estático en la clase Aplicación en el espacio de nombres System.Windows.Forms. System.Windows.Forms.Application.DoEvents () se puede usar para procesar los mensajes que esperan en la cola en el subproceso de la interfaz de usuario al realizar una tarea de larga ejecución en el subproceso de la interfaz de usuario. Esto tiene el beneficio de hacer que la IU parezca más receptiva y no "bloqueada" mientras se ejecuta una tarea larga. Sin embargo, esto casi nunca es la mejor manera de hacer las cosas. Según Microsoft llamando a DoEvents "... hace que se suspenda el hilo actual mientras se procesan todos los mensajes de la ventana de espera". Si se activa un evento, existe la posibilidad de errores inesperados e intermitentes que son difíciles de rastrear. Si tiene una tarea extensa, es mucho mejor hacerlo en un hilo separado. La ejecución de tareas largas en un subproceso separado les permite procesarse sin interferir con la interfaz de usuario que continúa funcionando sin problemas. MiraAquí para más detalles.

Aquí hay un ejemplo de cómo usar DoEvents; tenga en cuenta que Microsoft también proporciona una advertencia contra su uso.

Bill W
fuente
13

Desde mi experiencia, recomendaría una gran precaución al usar DoEvents en .NET. Experimenté algunos resultados muy extraños al usar DoEvents en un TabControl que contiene DataGridViews. Por otro lado, si todo lo que está tratando es un pequeño formulario con una barra de progreso, entonces podría estar bien.

La conclusión es: si va a utilizar DoEvents, debe probarlo a fondo antes de implementar su aplicación.

Craig Johnston
fuente
Buena respuesta, pero si puedo sugerir una solución para la barra de progreso que es mucho mejor , diría, haga su trabajo en un hilo separado, haga que su indicador de progreso esté disponible en una variable volátil y entrelazada, y actualice su barra de progreso desde un temporizador . De esta manera, ningún codificador de mantenimiento está tentado a agregar código pesado a su ciclo.
mg30rg
1
DoEvents o un equivalente es inevitable si tiene procesos de IU fuertes y no desea bloquear la IU. La primera opción es no hacer grandes porciones de procesamiento de IU, pero esto puede hacer que el código sea menos mantenible. Sin embargo, esperar a Dispatcher.Yield () hace algo muy similar a DoEvents y puede permitir esencialmente que se realice un proceso de IU que bloquee la pantalla para todos los intentos y propósitos asíncronos.
Desarrollador de Melbourne el
11

Si.

Sin embargo, si necesita usar Application.DoEvents, esto es principalmente una indicación de un mal diseño de la aplicación. ¿Quizás le gustaría hacer un trabajo en un hilo separado en su lugar?

Frederik Gheysels
fuente
3
¿Qué pasa si lo desea para poder girar y esperar a que se complete el trabajo en otro hilo?
jheriko
@jheriko Entonces deberías probar async-wait.
mg30rg
5

Vi el comentario de jheriko arriba e inicialmente estuve de acuerdo en que no podría encontrar una manera de evitar usar DoEvents si terminas girando tu hilo principal de la interfaz de usuario esperando que se complete un fragmento de código asincrónico de larga ejecución en otro hilo. Pero de la respuesta de Matthias, una simple actualización de un pequeño panel en mi interfaz de usuario puede reemplazar los DoEvents (y evitar un efecto secundario desagradable).

Más detalles sobre mi caso ...

Estaba haciendo lo siguiente (como se sugiere aquí ) para asegurar que una pantalla de presentación tipo barra de progreso ( Cómo mostrar una superposición de "carga" ... ) se actualizara durante un comando SQL de larga ejecución:

IAsyncResult asyncResult = sqlCmd.BeginExecuteNonQuery();
while (!asyncResult.IsCompleted)  //UI thread needs to Wait for Async SQL command to return
{
      System.Threading.Thread.Sleep(10); 
      Application.DoEvents();  //to make the UI responsive
}

Lo malo: para mí, llamar a DoEvents significaba que los clics del mouse a veces se activaban en formularios detrás de mi pantalla de inicio, incluso si lo hacía TopMost.

La buena / respuesta: Sustituir la línea DoEvents con una simple llamada de actualización a un pequeño panel en el centro de mi pantalla de bienvenida, FormSplash.Panel1.Refresh(). La interfaz de usuario se actualiza muy bien y la rareza de DoEvents de la que otros han advertido desapareció.

TamW
fuente
3
Sin embargo, Actualizar no actualiza la ventana. Si un usuario selecciona otra ventana en el escritorio, hacer clic de nuevo en su ventana no tendrá ningún efecto y el sistema operativo mostrará su aplicación como no responde. DoEvents () hace mucho más que una actualización, ya que interactúa con el sistema operativo a través del sistema de mensajería.
ThunderGr
4

He visto muchas aplicaciones comerciales, utilizando el "DoEvents-Hack". Especialmente cuando entra en juego el renderizado, a menudo veo esto:

while(running)
{
    Render();
    Application.DoEvents();
}

Todos saben sobre el mal de ese método. Sin embargo, usan el truco, porque no conocen ninguna otra solución. Aquí hay algunos enfoques tomados de una publicación de blog de Tom Miller :

  • Configure su formulario para que todo el dibujo ocurra en WmPaint, y haga su representación allí. Antes del final del método OnPaint, asegúrese de hacer esto. Invalidate (); Esto hará que el método OnPaint se active de nuevo inmediatamente.
  • P / Invocar en la API de Win32 y llamar a PeekMessage / TranslateMessage / DispatchMessage. (Doevents en realidad hace algo similar, pero puede hacerlo sin las asignaciones adicionales).
  • Escriba su propia clase de formularios, que es un pequeño contenedor alrededor de CreateWindowEx, y obtenga un control completo sobre el bucle de mensajes. -Decida que el método DoEvents funciona bien para usted y quédese con él.
Matías
fuente
3

DoEvents permite al usuario hacer clic o escribir y activar otros eventos, y los hilos de fondo son un mejor enfoque.

Sin embargo, todavía hay casos en los que puede encontrarse con problemas que requieren mensajes de eventos de vaciado. Me encontré con un problema en el que el control RichTextBox ignoraba el método ScrollToCaret () cuando el control tenía mensajes en cola para procesar.

El siguiente código bloquea todas las entradas del usuario mientras ejecuta DoEvents:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Integrative.Desktop.Common
{
    static class NativeMethods
    {
        #region Block input

        [DllImport("user32.dll", EntryPoint = "BlockInput")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool BlockInput([MarshalAs(UnmanagedType.Bool)] bool fBlockIt);

        public static void HoldUser()
        {
            BlockInput(true);
        }

        public static void ReleaseUser()
        {
            BlockInput(false);
        }

        public static void DoEventsBlockingInput()
        {
            HoldUser();
            Application.DoEvents();
            ReleaseUser();
        }

        #endregion
    }
}
cat_in_hat
fuente
1
Siempre debe bloquear eventos cuando llame a eventos. De lo contrario, otros eventos en su aplicación se dispararán en respuesta y su aplicación podría comenzar a hacer dos cosas a la vez.
FastAl
2

Application.DoEvents puede crear problemas si se coloca algo diferente al procesamiento de gráficos en la cola de mensajes.

Puede ser útil para actualizar las barras de progreso y notificar al usuario sobre el progreso en algo como la construcción y carga de MainForm, si eso lleva un tiempo.

En una aplicación reciente que hice, utilicé DoEvents para actualizar algunas etiquetas en una pantalla de carga cada vez que se ejecuta un bloque de código en el constructor de mi MainForm. El hilo de la interfaz de usuario estaba, en este caso, ocupado con el envío de un correo electrónico en un servidor SMTP que no era compatible con las llamadas SendAsync (). Probablemente podría haber creado un subproceso diferente con los métodos Begin () y End () y llamar a Send (), pero ese método es propenso a errores y preferiría que el Formulario principal de mi aplicación no arroje excepciones durante la construcción.

Invitado123
fuente