Domanda

Trovo che il modello di eventi .NET sia tale che spesso solleverò un evento su un thread e lo ascolterò su un altro thread.Mi chiedevo quale sia il modo più pulito per eseguire il marshalling di un evento da un thread in background al thread dell'interfaccia utente.

Basandomi sui suggerimenti della community, ho usato questo:

// 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
}
È stato utile?

Soluzione

Un paio di osservazioni:

  • Non creare delegati semplici esplicitamente in un codice del genere a meno che tu non sia pre-2.0, quindi potresti utilizzare:
   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
               sender, 
               args);
  • Inoltre non è necessario creare e popolare l'array di oggetti perché il parametro args è di tipo "params", quindi puoi semplicemente passare l'elenco.

  • Probabilmente sarei favorevole Invoke Sopra BeginInvoke poiché quest'ultimo comporterà la chiamata asincrona del codice, il che potrebbe o meno essere quello che stai cercando, ma renderebbe difficile la propagazione delle eccezioni successive senza una chiamata a EndInvoke.Ciò che accadrebbe è che la tua app finirebbe per ottenere un file TargetInvocationException Invece.

Altri suggerimenti

Io ho del codice per questo in linea.È molto più carino degli altri suggerimenti;sicuramente dai un'occhiata.

Utilizzo del campione:

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 le dichiarazioni ridondanti dei delegati.

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
}

Per i non eventi, è possibile utilizzare il file System.Windows.Forms.MethodInvoker delegato o System.Action.

MODIFICARE:Inoltre, ogni evento ha un corrispondente EventHandler delegate quindi non è affatto necessario dichiararne nuovamente uno.

Ho creato la seguente classe di chiamata cross thread "universale" per il mio scopo, ma penso che valga la pena condividerla:

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 puoi semplicemente utilizzare SetAnyProperty() da un altro thread:

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

In questo esempio la classe KvaserCanReader sopra esegue il proprio thread ed effettua una chiamata per impostare la proprietà text dell'etichetta lb_Speed ​​sul modulo principale.

Penso che il modo più pulito sia decisamente per seguire il percorso AOP.Crea alcuni aspetti, aggiungi gli attributi necessari e non dovrai mai più controllare l'affinità del thread.

Utilizza il contesto di sincronizzazione se desideri inviare un risultato al thread dell'interfaccia utente.Avevo bisogno di modificare la priorità del thread, quindi sono passato dall'utilizzo dei thread del pool di thread (codice commentato) e ho creato un nuovo thread tutto mio.Ero ancora in grado di utilizzare il contesto di sincronizzazione per restituire se l'annullamento del database è riuscito o meno.

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

        }
    }

Mi sono sempre chiesto quanto sia costoso Sempre supponiamo che l'invocazione sia obbligatoria...

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

Come nota a margine interessante, l'associazione di WPF gestisce automaticamente il marshalling in modo da poter associare l'interfaccia utente alle proprietà dell'oggetto modificate nei thread in background senza dover eseguire alcuna operazione speciale.Questo si è rivelato un grande risparmio di tempo per me.

Nell'XAML:

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

Puoi provare a sviluppare una sorta di componente generico che accetti a Contesto di sincronizzazione come input e lo usa per richiamare gli eventi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top