Modo più semplice per richiamare eventi cross-thread

Trovo che il modello di eventi .NET sia tale che creerò spesso un evento su un thread e lo ascolterò su un altro thread. Mi stavo chiedendo quale sia il modo più pulito per eseguire il marshal di un evento da un thread in background sul thread dell’interfaccia utente.

Sulla base dei 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 } 

Un paio di osservazioni:

  • Non creare delegati semplici in codice in modo esplicito, a meno che tu non sia pre-2.0, quindi potresti usare:
  BeginInvoke(new EventHandler(mCoolObject_CoolEvent), sender, args); 
  • Inoltre non è necessario creare e popolare l’array di oggetti perché il parametro args è di tipo “params” in modo da poter semplicemente passare l’elenco.

  • Probabilmente preferirei Invoke a BeginInvoke quanto quest’ultimo risulterà nel fatto che il codice venga chiamato in modo asincrono, il che potrebbe o meno essere quello che stai cercando, ma renderebbe difficile la propagazione delle eccezioni successive senza una chiamata a EndInvoke . Quello che succederebbe è che la tua app finirà per ottenere invece una TargetInvocationException .

Ho un codice per questo online. È molto più bello degli altri suggerimenti; sicuramente check it out.

Esempio di utilizzo:

 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 dichiarazioni delegate ridondanti.

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

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

EDIT: Inoltre, ogni evento ha un delegato EventHandler corrispondente quindi non è necessario affatto ridichiarne uno.

Penso che il modo più pulito sia sicuramente quello di seguire la rotta AOP. Apporta alcuni aspetti, aggiungi gli attributi necessari e non dovrai mai più controllare l’affinità dei thread.

Ho fatto la seguente class di chiamata “universale” a thread incrociati 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 usare SetAnyProperty () da un altro thread:

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

In questo esempio la class KvaserCanReader di cui sopra esegue il proprio thread ed effettua una chiamata per impostare la proprietà text dell’etichetta lb_Speed ​​nel modulo principale.

Utilizzare il contesto di sincronizzazione se si desidera inviare un risultato al thread dell’interfaccia utente. Avevo bisogno di cambiare la priorità del thread, così ho cambiato da thread di thread thread (codice commentato) e creato un nuovo thread per conto 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; ///  /// Gets the synchronization context used for UI-related operations. ///  /// The synchronization context. 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()); } } 

Come interessante nota a margine, il binding di WPF gestisce il marshalling automatico in modo da poter associare l’interfaccia utente alle proprietà dell’object che vengono modificate sui thread in background senza dover fare nulla di speciale. Questo ha dimostrato di essere un grande risparmio di tempo per me.

In XAML:

  

Mi sono sempre chiesto quanto sia costoso presumere sempre che sia richiesto il richiamo …

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

Puoi provare a sviluppare una sorta di componente generico che accetta un SynchronizationContext come input e lo usa per richiamare gli eventi.