33 votes

Est-il approprié d'étendre le Contrôle à fournir en permanence sûr Invoquer/BeginInvoke fonctionnalité?

Au cours de mon entretien pour un ancien de l'application qui en a bien violé la croix-fil de mise à jour des règles en winforms, j'ai créé l'extension suivante de la méthode comme un moyen de résoudre rapidement illégale des appels lorsque j'ai découvert d'eux:

/// <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();
    }
}

Exemple d'utilisation:

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

J'aime la façon dont je peux tirer des fermetures à lire, aussi, bien que forceSynchronous doit être vrai dans ce cas:

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

Je ne doute pas de l'utilité de cette méthode pour la fixation illégale des appels dans l'ancien code, mais qu'un nouveau code?

Est-il bon de conception pour utiliser cette méthode pour mettre à jour l'INTERFACE utilisateur dans un morceau de nouveaux logiciels lorsque vous ne pouvez pas savoir ce thread est de tenter de mettre à jour l'interface utilisateur, ou si de nouveaux Winforms code contiennent généralement un spécifique, dédié méthode appropriée Invoke()liés à la plomberie pour de telles mises à jour de l'INTERFACE utilisateur? (Je vais essayer d'utiliser l'autre traitement en arrière-plan des techniques, bien sûr, par exemple, BackgroundWorker.)

Il est intéressant de noter cela ne fonctionne pas pour ToolStripItems. Je viens de découvrir qu'elles dérivent directement à partir de Composant au lieu de Contrôle. Au lieu de cela, le contenant ToolStrips'invoquer doit être utilisé.

Suivi des commentaires:

Certains commentaires laissent à penser que:

if (uiElement.InvokeRequired)

devrait être:

if (uiElement.InvokeRequired && uiElement.IsHandleCreated)

Considérez les points suivants documentation msdn:

Cela signifie que InvokeRequired peut retourne false si l'Invoquer n'est pas nécessaire (l'appel sur le même thread), ou si le contrôle a été créé sur un thread différent, mais le contrôle de l' la poignée n'a pas encore été créé.

Dans le cas où le contrôle de la poignée de la n'a pas encore été créé, vous devez pas simplement appeler les propriétés, les méthodes,les ou des événements sur le contrôle. Cela pourrait cause le contrôle de la poignée à être créé sur le thread d'arrière-plan, isoler le contrôle sur un fil sans un message de la pompe et de faire le application instable.

Vous pouvez vous protéger contre ce cas, par aussi, en vérifiant la valeur de IsHandleCreated quand InvokeRequired retourne false en cas d'un thread d'arrière-plan.

Si le contrôle a été créé sur un autre fil, mais le contrôle de la poignée n'a pas encore été créé, InvokeRequired renvoie la valeur false. Cela signifie que si InvokeRequired retours true, IsHandleCreated sera toujours vrai. Tester de nouveau est redondante et incorrect.

11voto

Samuel Points 21085

Vous devez créer début et de Fin de l'extension de l'utilisation de méthodes. Et si vous utilisez les médicaments génériques, vous pouvez prendre l'appel de 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 d'obtenir un peu plus court et plus:

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, il suffit d'invoquer sur le formulaire ou le conteneur lui-même.

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

5voto

Charlie Flowers Points 9145

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

Voici un lien d'en parler. Il y a d'autres personnes aussi bien.

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

0voto

Earth Engine Points 1795

Ce n'est pas vraiment une réponse mais des réponses à certaines des observations pour la accepté de répondre.

Pour la norme IAsyncResult de motifs, de la BeginXXX méthode contient AsyncCallback paramètre, donc si vous voulez dire "je ne m'inquiète pas à ce sujet-appelez simplement EndInvoke quand c'est fait, et ignorer le résultat", vous pouvez faire quelque chose comme ceci (c'est pour Action , mais devrait pouvoir être ajusté pour d'autres types délégués):

    ...
    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 de solution à ne pas avoir une fonction d'assistance sans déclarer une variable à chaque fois lors de l'utilisation de ce modèle).

Mais pour l' Control.BeginInvoke nous n'avons pas d' AsyncCallBack, donc il n'est pas facile d'exprimer ce avec Control.EndInvoke garanti d'être appelé. Je pense que c'est pourquoi dans MSDN Control.EndInvoke dit être en option.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X