Question

Je trouve que le modèle d'événement .NET est tel que je déclenche souvent un événement sur un thread et l'écoute sur un autre thread.Je me demandais quelle était la manière la plus propre de rassembler un événement à partir d'un fil d'arrière-plan vers mon fil 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
}
Était-ce utile?

La solution

Quelques observations :

  • Ne créez pas de délégués simples explicitement dans un code comme celui-ci, sauf si vous êtes pré-2.0 afin de pouvoir utiliser :
   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
               sender, 
               args);
  • De plus, vous n'avez pas besoin de créer et de remplir le tableau d'objets car le paramètre args est de type "params", vous pouvez donc simplement le transmettre dans la liste.

  • je préférerais probablement Invoke sur BeginInvoke car ce dernier entraînera l'appel du code de manière asynchrone, ce qui peut ou non être ce que vous recherchez, mais rendrait la gestion des exceptions ultérieures difficile à propager sans un appel à EndInvoke.Ce qui se passerait, c'est que votre application finirait par obtenir un TargetInvocationException plutôt.

Autres conseils

J'ai du code pour ça en ligne.C'est beaucoup plus sympa que les autres suggestions ;vérifiez-le certainement.

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

J'évite les déclarations redondantes des délégués.

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
}

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

MODIFIER:De plus, chaque événement a un correspondant EventHandler déléguez donc il n’est pas du tout nécessaire d’en redéclarer un.

J'ai créé la classe d'appels croisés « universelle » suivante pour mes propres besoins, 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, 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;
      }
    }
  }

Et vous pouvez simplement utiliser SetAnyProperty() depuis un autre thread :

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

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

Je pense que la manière la plus propre est certainement pour emprunter la voie AOP.Créez quelques aspects, ajoutez les attributs nécessaires et vous n'aurez plus jamais à vérifier l'affinité des threads.

Utilisez le contexte de synchronisation si vous souhaitez envoyer un résultat au thread de l'interface utilisateur.J'avais besoin de changer la priorité du thread, j'ai donc cessé d'utiliser les threads du pool de threads (code commenté) et j'ai créé mon propre thread.J'étais toujours en mesure d'utiliser le contexte de synchronisation pour indiquer si l'annulation de la base de données avait réussi ou non.

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

        }
    }

Je me suis toujours demandé combien cela coûtait toujours supposons que l'invocation est requise...

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

Remarque intéressante, la liaison de WPF gère automatiquement le marshaling afin que vous puissiez lier l'interface utilisateur aux propriétés d'objet qui sont modifiées sur les threads d'arrière-plan sans avoir à faire quoi que ce soit de spécial.Cela s’est avéré être un gain de temps considérable pour moi.

En XAML :

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

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top