2008-08-22 8 views
73

Me parece que el modelo de eventos .NET es tal que a menudo plantearé un evento en un hilo y lo escucharé en otro hilo. Me preguntaba cuál es la forma más limpia de ordenar un evento desde un hilo de fondo en mi hilo de interfaz de usuario.Manera más limpia de invocar sucesos cruzados

Sobre la base de las sugerencias de la comunidad, he utilizado esto:

// earlier in the code 
mCoolObject.CoolEvent+= 
      new CoolObjectEventHandler(mCoolObject_CoolEvent); 
// then 
private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args) 
{ 
    if (InvokeRequired) 
    { 
     CoolObjectEventHandler cb = 
      new CoolObjectEventHandler(
       mCoolObject_CoolEvent); 
     Invoke(cb, new object[] { sender, args }); 
     return; 
    } 
    // do the dirty work of my method here 
} 
+0

Tenga en cuenta que InvokeRequired puede devolver false cuando un Control administrado existente aún no tiene un controlador no administrado. Debe tener precaución en los eventos que se plantearán antes de que se haya creado el control por completo. – GregC

Respuesta

24

Un par de observaciones:

  • No cree delegados simples de forma explícita en código así a menos que sea pre-2.0 para que pueda usar:
BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
       sender, 
       args); 
  • Además, no es necesario crear y completar la matriz de objetos porque el parámetro args es del tipo "params", por lo que puede pasar la lista.

  • que probablemente favorecen Invoke sobre BeginInvoke ya que este último se traducirá en el código que se llama de forma asincrónica, que puede o no puede ser lo que está buscando, pero haría que el manejo de excepciones posteriores difíciles de propagar sin una llamada a EndInvoke. Lo que sucedería es que su aplicación terminará obteniendo un TargetInvocationException en su lugar.

11

que rechazan las declaraciones de los delegados redundantes.

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args) 
{ 
    if (InvokeRequired) 
    { 
     Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args); 
     return; 
    } 
    // do the dirty work of my method here 
} 

Para los no-eventos, puede utilizar la System.Windows.Forms.MethodInvoker delegado o System.Action.

EDITAR: Además, cada evento tiene un delegado EventHandler correspondiente por lo que no hay necesidad de redeclarar uno.

+1

Para mí funcionó de esta manera: 'Invoke (nueva Acción (mCoolObject_CoolEvent), remitente, args);' –

+0

@ToniAlmeida Sí, eso fue un error en mi código. Gracias por mencionarlo. –

0

Siempre me he preguntado lo costoso que es para siempre asume se requiere que invocan ...

private void OnCoolEvent(CoolObjectEventArgs e) 
{ 
    BeginInvoke((o,e) => /*do work here*/,this, e); 
} 
+1

La realización de un BeginInvoke dentro de un subproceso de la GUI provocará que la acción en cuestión se posponga hasta la próxima vez que el subproceso de la interfaz de usuario procese los mensajes de Windows. En realidad, esto puede ser útil en algunos casos. – supercat

0

Puede tratar de desarrollar algún tipo de un componente genérico que acepta un SynchronizationContext como entrada y lo utiliza para invocar a los eventos.

2

Como una nota interesante, el enlace de WPF maneja el cálculo de referencias automáticamente para que pueda vincular la UI a las propiedades del objeto que se modifican en los hilos de fondo sin tener que hacer nada especial. Esto ha demostrado ser un gran ahorro de tiempo para mí.

En XAML:

<TextBox Text="{Binding Path=Name}"/> 
+0

esto no funcionará. una vez que estableces el puntal en el subproceso sin interfaz de usuario obtienes una excepción ... es decir, Name = "gbc" bang! falla ... no hay compañero de queso gratis –

+0

No es gratis (cuesta tiempo de ejecución), pero la maquinaria de enlace de wpf parece manejar automaticamente la clasificación cruzada de hilos. Usamos esto mucho con accesorios que se actualizan mediante datos de red recibidos en hilos de fondo. Hay una explicación aquí: http://blog.lab49.com/archives/1166 – gbc

+0

@gbc La explicación ha desaparecido 404. –

40

tengo some code for this en línea. Es mucho mejor que las otras sugerencias; definitivamente échale un vistazo.

Ejemplo de uso:

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args) 
{ 
    // You could use "() =>" in place of "delegate"; it's a style choice. 
    this.Invoke(delegate 
    { 
     // Do the dirty work of my method here. 
    }); 
} 
+2

+1 para demostrar la utilidad no-LINQ de los métodos de extensión. – galaktor

+0

Excelentes cosas. – Joe

+0

También podría cambiar el espacio de nombre a 'System.Windows.Forms' en su extensión. De esta forma evitará agregar _su espacio de nombres personalizado_ cada vez que lo necesite. –

3

creo que la manera más limpia es definitivamente ir a la ruta de AOP. Haga algunos aspectos, agregue los atributos necesarios y nunca más tendrá que verificar la afinidad de la secuencia.

+0

No entiendo su sugerencia. C# no es un lenguaje orientado a aspectos nativos. ¿Tiene en mente algún patrón o biblioteca para implementar aspectos que implementen el marshaling detrás de escena? – Eric

+0

Utilizo PostSharp, por lo que defino el comportamiento de subprocesamiento en una clase de atributo y luego uso, por ejemplo, el atributo [WpfThread] delante de cada método que se debe invocar en el subproceso de la interfaz de usuario. –

+0

Eso es fascinante ... tendré que probarlo. – Eric

2

hice la siguiente clase llamada hilo cruzado 'universal' para mi propio propósito, pero creo que vale la pena compartirlo:

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Windows.Forms; 

namespace CrossThreadCalls 
{ 
    public static class clsCrossThreadCalls 
    { 
    private delegate void SetAnyPropertyCallBack(Control c, string Property, object Value); 
    public static void SetAnyProperty(Control c, string Property, object Value) 
    { 
     if (c.GetType().GetProperty(Property) != null) 
     { 
     //The given property exists 
     if (c.InvokeRequired) 
     { 
      SetAnyPropertyCallBack d = new SetAnyPropertyCallBack(SetAnyProperty); 
      c.BeginInvoke(d, c, Property, Value); 
     } 
     else 
     { 
      c.GetType().GetProperty(Property).SetValue(c, Value, null); 
     } 
     } 
    } 

    private delegate void SetTextPropertyCallBack(Control c, string Value); 
    public static void SetTextProperty(Control c, string Value) 
    { 
     if (c.InvokeRequired) 
     { 
     SetTextPropertyCallBack d = new SetTextPropertyCallBack(SetTextProperty); 
     c.BeginInvoke(d, c, Value); 
     } 
     else 
     { 
     c.Text = Value; 
     } 
    } 
    } 

y se puede usar simplemente SetAnyProperty() desde otro hilo:

CrossThreadCalls.clsCrossThreadCalls.SetAnyProperty(lb_Speed, "Text", KvaserCanReader.GetSpeed.ToString()); 

En este ejemplo, la clase anterior KvaserCanReader ejecuta su propio hilo y realiza una llamada para establecer la propiedad de texto de la etiqueta lb_Speed ​​en el formulario principal.

2

Utilice el contexto de sincronización si desea enviar un resultado al subproceso de la interfaz de usuario. Necesitaba cambiar la prioridad del hilo, así que cambié el uso de los subprocesos del grupo de subprocesos (código comentado) y creé un nuevo hilo propio. Todavía pude usar el contexto de sincronización para devolver si la cancelación de la base de datos fue exitosa o no.

#region SyncContextCancel 

    private SynchronizationContext _syncContextCancel; 

    /// <summary> 
    /// Gets the synchronization context used for UI-related operations. 
    /// </summary> 
    /// <value>The synchronization context.</value> 
    protected SynchronizationContext SyncContextCancel 
    { 
     get { return _syncContextCancel; } 
    } 

    #endregion //SyncContextCancel 

    public void CancelCurrentDbCommand() 
    { 
     _syncContextCancel = SynchronizationContext.Current; 

     //ThreadPool.QueueUserWorkItem(CancelWork, null); 

     Thread worker = new Thread(new ThreadStart(CancelWork)); 
     worker.Priority = ThreadPriority.Highest; 
     worker.Start(); 
    } 

    SQLiteConnection _connection; 
    private void CancelWork()//object state 
    { 
     bool success = false; 

     try 
     { 
      if (_connection != null) 
      { 
       log.Debug("call cancel"); 
       _connection.Cancel(); 
       log.Debug("cancel complete"); 
       _connection.Close(); 
       log.Debug("close complete"); 
       success = true; 
       log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString()); 
      } 
     } 
     catch (Exception ex) 
     { 
      log.Error(ex.Message, ex); 
     } 

     SyncContextCancel.Send(CancelCompleted, new object[] { success }); 
    } 

    public void CancelCompleted(object state) 
    { 
     object[] args = (object[])state; 
     bool success = (bool)args[0]; 

     if (success) 
     { 
      log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString()); 

     } 
    } 
Cuestiones relacionadas