Pergunta

Acho que o modelo de evento .NET é tal que frequentemente gerarei um evento em um thread e ouvirei ele em outro thread.Eu queria saber qual é a maneira mais limpa de organizar um evento de um thread de segundo plano em meu thread de UI.

Com base nas sugestões da comunidade, usei isto:

// 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
}
Foi útil?

Solução

Algumas observações:

  • Não crie delegados simples explicitamente em código como esse, a menos que você seja pré-2.0 para poder usar:
   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
               sender, 
               args);
  • Além disso, você não precisa criar e preencher a matriz de objetos porque o parâmetro args é do tipo "params", então você pode simplesmente passar a lista.

  • Eu provavelmente preferiria Invoke sobre BeginInvoke já que o último resultará na chamada do código de forma assíncrona, o que pode ou não ser o que você procura, mas dificultaria a propagação de exceções subsequentes sem uma chamada para EndInvoke.O que aconteceria é que seu aplicativo acabaria recebendo um TargetInvocationException em vez de.

Outras dicas

Eu tenho algum código para isso on-line.É muito melhor que as outras sugestões;definitivamente dê uma olhada.

Uso de amostra:

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

Evito declarações redundantes de delegados.

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 não eventos, você pode usar o System.Windows.Forms.MethodInvoker delegar ou System.Action.

EDITAR:Além disso, cada evento tem um correspondente EventHandler delegar, então não há necessidade de redeclarar um.

Fiz a seguinte classe de chamada de thread cruzado 'universal' para meu próprio propósito, mas acho que vale a pena compartilhá-la:

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

E você pode simplesmente usar SetAnyProperty() de outro thread:

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

Neste exemplo, a classe KvaserCanReader acima executa seu próprio thread e faz uma chamada para definir a propriedade text do rótulo lb_Speed ​​no formulário principal.

Eu acho que a maneira mais limpa é definitivamente para seguir a rota AOP.Faça alguns aspectos, adicione os atributos necessários e você nunca mais precisará verificar a afinidade do thread.

Use o contexto de sincronização se quiser enviar um resultado para o thread de UI.Eu precisava alterar a prioridade do thread, então deixei de usar threads do pool de threads (código comentado) e criei meu próprio thread.Ainda consegui usar o contexto de sincronização para retornar se o cancelamento do banco de dados foi bem-sucedido ou não.

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

        }
    }

Sempre me perguntei o quão caro é sempre suponha que invocar seja necessário ...

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

Como uma observação interessante, a ligação do WPF lida com o empacotamento automaticamente para que você possa vincular a interface do usuário às propriedades do objeto que são modificadas em threads de segundo plano sem precisar fazer nada especial.Isso provou ser uma grande economia de tempo para mim.

Em XAML:

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

Você pode tentar desenvolver algum tipo de componente genérico que aceite um SincronizaçãoContexto como entrada e a utiliza para invocar os eventos.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top