Pregunta Manera más limpia de invocar eventos cruzados


Encuentro 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.

En base a las sugerencias de la comunidad, he usado 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
}

75
2017-08-22 13:34


origen


Respuestas:


Un par de observaciones:

  • No cree delegados sencillos explícitamente en un código como ese, a menos que sea pre-2.0, por lo que podría 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.

  • Probablemente preferiría Invoke encima BeginInvoke ya que esto último dará como resultado que el código se llame de manera asíncrona, lo que puede o no ser lo que está buscando, pero dificultaría la propagación de excepciones subsiguientes sin una llamada a EndInvoke. Lo que sucedería es que tu aplicación terminará obteniendo un TargetInvocationException en lugar.


25
2017-08-22 13:45



yo tengo algún código para esto en línea. Es mucho mejor que las otras sugerencias; definitivamente échale un vistazo.

Uso de muestra:

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.
    });
}

41
2017-11-03 11:30



Evito las declaraciones delegadas 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 no eventos, puede usar System.Windows.Forms.MethodInvoker delegar o System.Action.

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


11
2017-08-22 13:42



Creo que la forma más limpia es seguro para 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.


3
2018-04-29 17:33



Hice la siguiente clase de llamada de 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 simplemente puede usar SetAnyProperty () desde otro hilo:

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

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


3
2017-10-05 09:36



Use 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());

        }
    }

3
2017-07-07 08:20



Como nota al margen 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}"/>

2
2017-08-22 14:20



Siempre me he preguntado qué tan costoso es siempre supongamos que se requiere invocación ...

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

1
2017-08-22 13:44



Puede intentar desarrollar algún tipo de componente genérico que acepte un SynchronizationContext como entrada y lo usa para invocar los eventos.


0
2017-08-22 13:53