Manière la plus propre d’invoquer des événements transversaux

Je trouve que le modèle d’événement .NET est tel que je vais souvent soulever un événement sur un thread et l’écouter sur un autre thread. Je me demandais quel est le moyen le plus propre de rassembler un événement depuis un thread d’arrière-plan sur mon thread d’interface utilisateur.

Sur la base des suggestions de la communauté, j’ai utilisé ceci:

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

Quelques observations:

  • Ne créez pas de delegates simples explicitement dans un code comme celui-ci, sauf si vous êtes pré-2.0, vous pouvez donc utiliser:
  BeginInvoke(new EventHandler(mCoolObject_CoolEvent), sender, args); 
  • De plus, vous n’avez pas besoin de créer et de remplir le tableau d’objects car le paramètre args est un type “params”, vous pouvez donc simplement passer la liste.

  • Je préférerais probablement Invoke over BeginInvoke car ce dernier aurait pour effet d’appeler le code de manière asynchrone, ce qui peut être ou ne pas être ce que vous recherchez, mais rendrait difficile la propagation des exceptions suivantes sans appel à EndInvoke . Ce qui arriverait, c’est que votre application finira par recevoir une TargetInvocationException .

J’ai du code pour cela en ligne. C’est beaucoup mieux que les autres suggestions; certainement le vérifier.

Exemple d’utilisation:

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

Je refuse les déclarations de delegates redondantes.

 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 } 

Pour les non-événements, vous pouvez utiliser le délégué System.Windows.Forms.MethodInvoker ou System.Action .

EDIT: De plus, chaque événement a un délégué EventHandler correspondant, il n’y a donc aucun besoin de le redéclarer.

Je pense que le moyen le plus propre est de suivre la voie AOP. Faites quelques aspects, ajoutez les atsortingbuts nécessaires et vous ne devez plus vérifier l’affinité des threads.

J’ai créé la classe d’appels «universelle» suivante pour mon propre objective, mais je pense que cela vaut la peine de la partager:

 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, ssortingng Property, object Value); public static void SetAnyProperty(Control c, ssortingng 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, ssortingng Value); public static void SetTextProperty(Control c, ssortingng Value) { if (c.InvokeRequired) { SetTextPropertyCallBack d = new SetTextPropertyCallBack(SetTextProperty); c.BeginInvoke(d, c, Value); } else { c.Text = Value; } } } 

Et vous pouvez simplement utiliser SetAnyProperty () à partir d’un autre thread:

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

Dans cet exemple, la classe KvaserCanReader ci-dessus exécute son propre thread et appelle pour définir la propriété text de l’étiquette lb_Speed ​​sur le formulaire principal.

Utilisez le contexte de synchronisation si vous souhaitez envoyer un résultat au thread d’interface utilisateur. J’avais besoin de changer la priorité des threads afin de ne plus utiliser les threads de pool de threads (code commenté) et de créer un nouveau thread. Je pouvais toujours utiliser le contexte de synchronisation pour savoir si la firebase database était annulée ou non.

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

En guise de remarque intéressante, la liaison de WPF gère le marshaling automatiquement afin que vous puissiez lier l’interface utilisateur aux propriétés d’object modifiées sur les threads d’arrière-plan sans avoir à faire quelque chose de spécial. Cela s’est avéré être un bon gain de temps pour moi.

Dans XAML:

  

Je me suis toujours demandé combien il était coûteux de toujours supposer que l’invocation était nécessaire …

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

Vous pouvez essayer de développer une sorte de composant générique qui accepte un SynchronizationContext en entrée et l’utilise pour appeler les événements.