Éstos son los puntos más destacados:
- No se pueden realizar llamadas de control de interfaz de usuario de un hilo diferente a la que fueron creados en (rosca de la forma).
- Las invocaciones de delegado (es decir, anzuelos de evento) se desencadenan en el mismo subproceso que el objeto que está disparando el evento.
Por lo tanto, si tiene un hilo de "motor" separado haciendo algún trabajo y tiene alguna interfaz de usuario para ver los cambios de estado que se pueden reflejar en la IU (como una barra de progreso o algo así), tiene un problema. El incendio del motor es un evento modificado por un objeto que ha sido enganchado por el Formulario. Pero el delegado de devolución de llamada que llama al Formulario registrado con el motor en el hilo del motor ... no en el hilo del Formulario. Y entonces no puedes actualizar ningún control de esa devolución de llamada. Doh!
BeginInvoke viene al rescate. Sólo tiene que utilizar este modelo de codificación simple en todas sus métodos de devolución de llamada y usted puede estar seguro de que las cosas van a estar bien:
private delegate void EventArgsDelegate(object sender, EventArgs ea);
void SomethingHappened(object sender, EventArgs ea)
{
//
// Make sure this callback is on the correct thread
//
if (this.InvokeRequired)
{
this.Invoke(new EventArgsDelegate(SomethingHappened), new object[] { sender, ea });
return;
}
//
// Do something with the event such as update a control
//
textBox1.Text = "Something happened";
}
Es bastante simple en realidad.
- Uso InvokeRequired para averiguar si esta devolución de llamada que sucedió en la secuencia correcta.
- De lo contrario, reinicie la devolución de llamada en la secuencia correcta con los mismos parámetros. Puede reinvocar un método utilizando los métodos Invoke (bloqueo) o BeginInvoke (sin bloqueo).
- La próxima vez que se llame a la función, InvokeRequired devuelve falso porque ahora estamos en el hilo correcto y todo el mundo está contento.
Esta es una forma muy compacta de hacer frente a este problema y hacer salvo de las devoluciones de llamada de eventos multi-hilo sus formularios.
Generalmente prefiero BeginInvoke a Invoke, pero hay una advertencia: uno debe evitar hacer cola en demasiados eventos. Utilizo una variable updateRequired que está configurada en 1 cuando ocurriría un BeginInvoke, y solo realizo el BeginInvoke si hubiera sido cero (usando Interlocked.Exchange). El controlador de pantalla tiene un ciclo while que borra updateRequired y, si no era cero, realiza una actualización y realiza un bucle. En algunos casos, se agrega un temporizador para limitar aún más la frecuencia de actualización (para evitar que el código gaste todo el tiempo actualizando la lectura del progreso en lugar de hacer un trabajo real), pero eso es más complicado. – supercat
@Supercat ... la aceleración de eventos es un tema importante para muchas aplicaciones, pero no es algo que deba ser parte de la capa de UI. Se debe crear un bus proxy de eventos separado para recibir, poner en cola, combinar y reenviar eventos a intervalos apropiados. Cualquier suscriptor al bus de eventos no debe saber que está ocurriendo un límite de eventos. –
Puedo ver lugares donde podría ser útil un "bus de eventos" separado para manejar la sincronización, pero en muchos casos parecería más fácil para el usuario final algo así como una clase de indicador de progreso si la clase simplemente expuso una propiedad MinimumUpdateInterval. – supercat