visual shown que net formulario form eventos evento detectar control cargar activo c# .net winforms multithreading events

c# - shown - system windows forms



¿Cómo puedo hacer que las devoluciones de eventos en mis formularios de win sean seguras? (6)

Como el lazy programmer , tengo un método muy perezoso de hacer esto.

Lo que hago es simplemente esto.

private void DoInvoke(MethodInvoker del) { if (InvokeRequired) { Invoke(del); } else { del(); } } //example of how to call it private void tUpdateLabel(ToolStripStatusLabel lbl, String val) { DoInvoke(delegate { lbl.Text = val; }); }

Puede alinear el DoInvoke dentro de su función o esconderlo en una función separada para hacer el trabajo sucio por usted.

Solo tenga en cuenta que puede pasar funciones directamente al método DoInvoke.

private void directPass() { DoInvoke(this.directInvoke); } private void directInvoke() { textLabel.Text = "Directly passed."; }

Cuando te suscribes a un evento en un objeto desde un formulario, básicamente estás cediendo el control de tu método de devolución de llamada al origen del evento. No tiene idea si esa fuente de eventos elegirá activar el evento en un hilo diferente.

El problema es que cuando se invoca la devolución de llamada, no puede suponer que puede realizar controles de actualización en su formulario porque a veces esos controles arrojarán una expection si la devolución de llamada de evento fue invocada en un hilo diferente al hilo en el que se ejecutó el formulario.


En muchos casos simples, puede usar el delegado de MethodInvoker y evitar la necesidad de crear su propio tipo de delegado.


Estos son los puntos destacados:

  1. No puede hacer llamadas de control de UI desde un hilo diferente al que se creó (el hilo del formulario).
  2. Las invocaciones de delegado (es decir, ganchos de evento) se desencadenan en el mismo hilo que el objeto que está disparando el evento.

Por lo tanto, si tiene un subproceso de "motor" separado que hace algo de trabajo y tiene alguna interfaz de usuario que observa los cambios de estado que pueden reflejarse en la interfaz de usuario (como una barra de progreso o lo que sea), 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. Simplemente use este modelo de codificación simple en todos sus métodos de devolución de llamada y 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.

  1. Use InvokeRequired para averiguar si esta devolución de llamada ocurrió en el hilo correcto.
  2. 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 (no bloqueo).
  3. La próxima vez que se llame a la función, InvokeRequired devuelve falso porque ahora estamos en el hilo correcto y todos están contentos.

Esta es una forma muy compacta de abordar este problema y proteger sus formularios de devoluciones de eventos con múltiples subprocesos.


Llego un poco tarde a este tema, pero es posible que desee echar un vistazo al Patrón asincrónico basado en eventos . Cuando se implementa correctamente, garantiza que los eventos siempre se generan desde el hilo de la interfaz de usuario.

Aquí hay un breve ejemplo que solo permite una invocación simultánea; El soporte de múltiples invocaciones / eventos requiere un poco más de plomería.

using System; using System.ComponentModel; using System.Threading; using System.Windows.Forms; namespace WindowsFormsApplication1 { public class MainForm : Form { private TypeWithAsync _type; [STAThread()] public static void Main() { Application.EnableVisualStyles(); Application.Run(new MainForm()); } public MainForm() { _type = new TypeWithAsync(); _type.DoSomethingCompleted += DoSomethingCompleted; var panel = new FlowLayoutPanel() { Dock = DockStyle.Fill }; var btn = new Button() { Text = "Synchronous" }; btn.Click += SyncClick; panel.Controls.Add(btn); btn = new Button { Text = "Asynchronous" }; btn.Click += AsyncClick; panel.Controls.Add(btn); Controls.Add(panel); } private void SyncClick(object sender, EventArgs e) { int value = _type.DoSomething(); MessageBox.Show(string.Format("DoSomething() returned {0}.", value)); } private void AsyncClick(object sender, EventArgs e) { _type.DoSomethingAsync(); } private void DoSomethingCompleted(object sender, DoSomethingCompletedEventArgs e) { MessageBox.Show(string.Format("DoSomethingAsync() returned {0}.", e.Value)); } } class TypeWithAsync { private AsyncOperation _operation; // synchronous version of method public int DoSomething() { Thread.Sleep(5000); return 27; } // async version of method public void DoSomethingAsync() { if (_operation != null) { throw new InvalidOperationException("An async operation is already running."); } _operation = AsyncOperationManager.CreateOperation(null); ThreadPool.QueueUserWorkItem(DoSomethingAsyncCore); } // wrapper used by async method to call sync version of method, matches WaitCallback so it // can be queued by the thread pool private void DoSomethingAsyncCore(object state) { int returnValue = DoSomething(); var e = new DoSomethingCompletedEventArgs(returnValue); _operation.PostOperationCompleted(RaiseDoSomethingCompleted, e); } // wrapper used so async method can raise the event; matches SendOrPostCallback private void RaiseDoSomethingCompleted(object args) { OnDoSomethingCompleted((DoSomethingCompletedEventArgs)args); } private void OnDoSomethingCompleted(DoSomethingCompletedEventArgs e) { var handler = DoSomethingCompleted; if (handler != null) { handler(this, e); } } public EventHandler<DoSomethingCompletedEventArgs> DoSomethingCompleted; } public class DoSomethingCompletedEventArgs : EventArgs { private int _value; public DoSomethingCompletedEventArgs(int value) : base() { _value = value; } public int Value { get { return _value; } } } }


Para simplificar un poco el código de Simon, podría usar el delegado de acción genérico incorporado. Le ahorra a su código un montón de tipos de delegados que realmente no necesita. Además, en .NET 3.5 agregaron un parámetro params al método Invoke para que no tenga que definir una matriz temporal.

void SomethingHappened(object sender, EventArgs ea) { if (InvokeRequired) { Invoke(new Action<object, EventArgs>(SomethingHappened), sender, ea); return; } textBox1.Text = "Something happened"; }


Utilizo mucho los métodos anónimos en este escenario:

void SomethingHappened(object sender, EventArgs ea) { MethodInvoker del = delegate{ textBox1.Text = "Something happened"; }; InvokeRequired ? Invoke( del ) : del(); }