Est-il approprié d'étendre le contrôle de fournir systématiquement en toute sécurité la fonctionnalité Invoke / BeginInvoke?

StackOverflow https://stackoverflow.com/questions/714666

Question

Au cours de mon entretien d'une ancienne application mal violé les règles de mise à jour croisée fil dans WinForms, j'ai créé la méthode d'extension suivante comme un moyen de corriger rapidement les appels illégaux quand je les ai découvert:

/// <summary>
/// Execute a method on the control's owning thread.
/// </summary>
/// <param name="uiElement">The control that is being updated.</param>
/// <param name="updater">The method that updates uiElement.</param>
/// <param name="forceSynchronous">True to force synchronous execution of 
/// updater.  False to allow asynchronous execution if the call is marshalled
/// from a non-GUI thread.  If the method is called on the GUI thread,
/// execution is always synchronous.</param>
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
    if (uiElement == null)
    {
        throw new ArgumentNullException("uiElement");
    }

    if (uiElement.InvokeRequired)
    {
        if (forceSynchronous)
        {
            uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
        else
        {
            uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
    }
    else
    {
        if (!uiElement.IsHandleCreated)
        {
            // Do nothing if the handle isn't created already.  The user's responsible
            // for ensuring that the handle they give us exists.
            return;
        }

        if (uiElement.IsDisposed)
        {
            throw new ObjectDisposedException("Control is already disposed.");
        }

        updater();
    }
}

Utilisation de l'échantillon:

this.lblTimeDisplay.SafeInvoke(() => this.lblTimeDisplay.Text = this.task.Duration.ToString(), false);

J'aime la façon dont je peux tirer parti de fermetures à lire, aussi, si besoin forceSynchronous être vrai dans ce cas:

string taskName = string.Empty;
this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true);

Je ne remets pas en question l'utilité de cette méthode pour la fixation des appels illégaux dans le code existant, mais qu'en est-nouveau code?

est-il une bonne conception d'utiliser cette méthode pour mettre à jour l'interface utilisateur dans un nouveau morceau de logiciel quand vous ne savez pas quel fil tente de mettre à jour l'interface, ou si nouveau code Winforms contiennent généralement une méthode spécifique, dédiée à la Invoke() appropriée plomberie pour toutes ces concernant la PI mises à jour l'interface utilisateur? (Je vais essayer d'utiliser les autres techniques appropriées de traitement de fond d'abord, bien sûr, par exemple BackgroundWorker.)

Il est intéressant cela ne fonctionnera pas pour ToolStripItems . Je viens récemment découvert qu'ils dérivent directement de Component au lieu de contrôle. Au lieu de cela, l'invocation du contenant ToolStrip doit être utilisé.

Followup aux commentaires:

Certains commentaires suggèrent que:

if (uiElement.InvokeRequired)

devrait être:

if (uiElement.InvokeRequired && uiElement.IsHandleCreated)

Considérez ce qui suit msdn documentation :

  

Cela signifie que InvokeRequired peut    return false si Invoke n'est pas nécessaire   (L'appel se produit sur le même fil),   ou si le contrôle a été créé sur un   thread différent mais du contrôle   poignée n'a pas encore été créé.

     

Dans le cas où la poignée de la commande   n'a pas encore été créé, vous devriez   pas simplement appeler propriétés, méthodes,   ou des événements sur le contrôle. Cela pourrait   provoquer la poignée de la commande être   créé sur le fil de fond,   l'isolement de la commande sur un fil   sans pompe de message et de faire la   l'application instable.

     

Vous pouvez protéger contre ce cas par   vérifier également la valeur de   IsHandleCreated quand InvokeRequired   retourne false sur un thread d'arrière-plan.

Si le contrôle a été créé sur un thread différent, mais la poignée de la commande n'a pas encore été créé, InvokeRequired retourne false. Cela signifie que si le rendement de InvokeRequired true, IsHandleCreated sera toujours vrai. Test il est à nouveau redondant et incorrect.

Était-ce utile?

La solution

J'aime l'idée générale, mais je vois un problème. Il est important de traiter EndInvokes, ou vous pouvez avoir des fuites de ressources. Je sais que beaucoup de gens ne croient pas, mais il est vraiment vrai.

Voilà un lien en parler . Il y a d'autres aussi.

Mais la principale réponse que j'ai est: Oui, je pense que vous avez une bonne idée ici

.

Autres conseils

Vous devez créer Begin et des méthodes d'extension de fin ainsi. Et si vous utilisez des médicaments génériques, vous pouvez faire l'appel regarder un peu plus agréable.

public static class ControlExtensions
{
  public static void InvokeEx<T>(this T @this, Action<T> action)
    where T : Control
  {
    if (@this.InvokeRequired)
    {
      @this.Invoke(action, new object[] { @this });
    }
    else
    {
      if (!@this.IsHandleCreated)
        return;
      if (@this.IsDisposed)
        throw new ObjectDisposedException("@this is disposed.");

      action(@this);
    }
  }

  public static IAsyncResult BeginInvokeEx<T>(this T @this, Action<T> action)
    where T : Control
  {
    return @this.BeginInvoke((Action)delegate { @this.InvokeEx(action); });
  }

  public static void EndInvokeEx<T>(this T @this, IAsyncResult result)
    where T : Control
  {
    @this.EndInvoke(result);
  }
}

Maintenant, vos appels sont un peu plus court et plus propre:

this.lblTimeDisplay.InvokeEx(l => l.Text = this.task.Duration.ToString());

var result = this.BeginInvokeEx(f => f.Text = "Different Title");
// ... wait
this.EndInvokeEx(result);

Et en ce qui concerne Components, invoquer uniquement sur la forme ou le conteneur lui-même.

this.InvokeEx(f => f.toolStripItem1.Text = "Hello World");

Ce n'est pas vraiment une réponse, mais répond à quelques commentaires pour la réponse acceptée.

Pour les modèles standard IAsyncResult, la méthode BeginXXX contient paramètre AsyncCallback, donc si vous voulez dire « Je ne me soucie pas de cela - il suffit d'appeler EndInvoke quand il est fait et ne pas tenir compte du résultat », vous pouvez faire quelque chose comme ça ( ceci est pour Action mais devrait être en mesure d'adapter pour les autres types de délégué):

    ...
    public static void BeginInvokeEx(this Action a){
        a.BeginInvoke(a.EndInvoke, a);
    }
    ...
    // Don't worry about EndInvoke
    // it will be called when finish
    new Action(() => {}).BeginInvokeEx(); 

(Malheureusement, je n'ai pas une solution de ne pas avoir une fonction d'assistance sans déclarer une variable chaque fois que l'utilisation de ce modèle).

Mais pour Control.BeginInvoke nous n'avons pas AsyncCallBack, donc il n'y a aucun moyen facile d'exprimer avec Control.EndInvoke garantie à appeler. La façon dont il a été conçu demande le fait que Control.EndInvoke est facultative.

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