46 votes

Nettoyage du code jonché d'InvokeRequired

Je sais que lors de la manipulation des contrôles d'INTERFACE utilisateur à partir de n'importe quel non-thread de l'INTERFACE utilisateur, vous devez mobiliser vos appels vers le thread d'INTERFACE utilisateur pour éviter les problèmes. Le consensus général est que vous devez utiliser test InvokeRequired, et si la valeur est true, l'utilisation .Invoquer pour effectuer le regroupement.

Cela conduit à beaucoup de code qui ressemble à ceci:

private void UpdateSummary(string text)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new Action(() => UpdateSummary(text)));
    }
    else
    {
        summary.Text = text;
    }
}

Ma question est: puis-je laisser le InvokeRequired d'essai et appelez simplement Invoquer, comme suit:

private void UpdateSummary(string text)
{
    this.Invoke(new Action(() => summary.Text = text));
}

Est-il un problème avec cela? Si oui, est-il une meilleure façon de garder la InvokeRequired test, tout en n'ayant pas à copier-coller le motif sur l'endroit?

65voto

John Gietzen Points 23645

Eh bien, qu'en est-il:

 public static class ControlHelpers
{
    public static void InvokeIfRequired(this ISynchronizeInvoke control, Action<ISynchronizeInvoke> action)
    {
        if (control.InvokeRequired)
        {
            control.Invoke(new Action(() => action(control)));
        }
        else
        {
            action(control);
        }
    }
}
 

Utilisez-le comme ceci:

 private void UpdateSummary(string text)
{
    summary.InvokeIfRequired(c => { textBox.Text = text });
}
 

8voto

SLaks Points 391154

Appeler Invoke partir du thread d'interface utilisateur est quelque peu inefficace.

Au lieu de cela, vous pouvez créer une méthode d'extension InvokeIfNeeded qui prend un paramètre Action . (cela vous permettrait également de supprimer new Action(...) du site d'appel)

7voto

Michael Points 1649

J'ai lu sur les arguments en arrière et en avant sur l'ajout d'une logique à vérifier pour savoir si l'appel doit être utilisé IFF lorsqu'il n'est pas sur le thread d'INTERFACE utilisateur et non pas sur le thread d'INTERFACE utilisateur lui-même. J'ai écrit une classe qui examine le temps d'exécution (via Chronomètre) de différentes méthodes pour obtenir une estimation approximative de l'efficacité d'une méthode plutôt qu'une autre.

Le résultat peut être surprenant pour certains d'entre vous (ces tests ont été exécutés via le Formulaire.Illustré de l'événement):

     // notice that we are updating the form's title bar 10,000 times
     // directly on the UI thread
     TimedAction.Go
     (
        "Direct on UI Thread",
        () =>
        {
           for (int i = 0; i < 10000; i++)
           {
              this.Text = "1234567890";
           }
        }
     );

     // notice that we are invoking the update of the title bar
     // (UI thread -> [invoke] -> UI thread)
     TimedAction.Go
     (
        "Invoke on UI Thread",
        () =>
        {
           this.Invoke
           (
              new Action
              (
                 () =>
                 {
                    for (int i = 0; i < 10000; i++)
                    {
                       this.Text = "1234567890";
                    }
                 }
              )
           );
        }
     );

     // the following is invoking each UPDATE on the UI thread from the UI thread
     // (10,000 invokes)
     TimedAction.Go
     (
        "Separate Invoke on UI Thread",
        () =>
        {
           for (int i = 0; i < 10000; i++)
           {
              this.Invoke
              (
                 new Action
                 (
                    () =>
                    {
                       this.Text = "1234567890";
                    }
                 )
              );
           }
        }
     );

Les résultats sont comme suit:

  • TimedAction::Go()+0 - Debug: [DEBUG] Chronomètre [Direct sur le Thread de l'INTERFACE utilisateur]: 300ms
  • TimedAction::Go()+0 - Debug: [DEBUG] Chronomètre [Invoquer sur le Thread de l'INTERFACE utilisateur]: 299ms
  • TimedAction::Go()+0 - Debug: [DEBUG] Chronomètre [Les Invoquer sur le Thread de l'INTERFACE utilisateur]: 649ms

Ma conclusion est que vous pouvez appeler à tout moment, indépendamment de savoir si vous êtes sur le thread d'INTERFACE utilisateur ou un thread de travail, sans surcharge importante de la boucle de retour via le message de la pompe. Toutefois, l'exécution de la plupart des travaux SUR le thread de l'INTERFACE utilisateur au lieu de faire de nombreux appels à la thread d'INTERFACE utilisateur (via Invoke()) est avantageux et d'améliorer grandement l'efficacité.

5voto

Mark Rushakoff Points 97350

Je me rends compte qu'il y a déjà une réponse qui est à peu près spot sur, mais je voulais aussi poster mon prendre sur elle (que j'ai également posté ici).

Le mien est un peu différent en ce qu'il peut légèrement plus de sécurité poignée null contrôles et peut retourner des résultats si nécessaire. Ces deux ont venir dans maniable pour moi quand j'Invoque montrant un MessageBox sur un formulaire parent qui peut être null, et le retour de la DialogResult de montrer que MessageBox.


using System;
using System.Windows.Forms;

/// <summary>
/// Extension methods acting on Control objects.
/// </summary>
internal static class ControlExtensionMethods
{
    /// <summary>
    /// Invokes the given action on the given control's UI thread, if invocation is needed.
    /// </summary>
    /// <param name="control">Control on whose UI thread to possibly invoke.</param>
    /// <param name="action">Action to be invoked on the given control.</param>
    public static void MaybeInvoke(this Control control, Action action)
    {
        if (control != null && control.InvokeRequired)
        {
            control.Invoke(action);
        }
        else
        {
            action();
        }
    }

    /// <summary>
    /// Maybe Invoke a Func that returns a value.
    /// </summary>
    /// <typeparam name="T">Return type of func.</typeparam>
    /// <param name="control">Control on which to maybe invoke.</param>
    /// <param name="func">Function returning a value, to invoke.</param>
    /// <returns>The result of the call to func.</returns>
    public static T MaybeInvoke<T>(this Control control, Func<T> func)
    {
        if (control != null && control.InvokeRequired)
        {
            return (T)(control.Invoke(func));
        }
        else
        {
            return func();
        }
    }
}

Utilisation:

myForm.MaybeInvoke(() => this.Text = "Hello world");

// Sometimes the control might be null, but that's okay.
var dialogResult = this.Parent.MaybeInvoke(() => MessageBox.Show(this, "Yes or no?", "Choice", MessageBoxButtons.YesNo));

3voto

Brian Gideon Points 26683

Je ne suis pas convaincu qu' Control.Invoke est le meilleur choix pour la mise à jour de l'INTERFACE utilisateur. Je ne peux pas dire à coup sûr dans votre cas, car je ne connais pas les circonstances dans lesquelles UpdateSummary dans appelé. Toutefois, si vous appelez régulièrement comme un mécanisme pour afficher la progression de l'information (qui est l'impression que je reçois de l'extrait de code), il est généralement une meilleure option. Cette option est d'avoir le thread d'INTERFACE utilisateur sondage pour l'état au lieu d'avoir le thread de le pousser à.

Les raisons pour lesquelles le scrutin approche doit être considérée dans ce cas, c'est parce que:

  • Il rompt le couplage étroit entre l'INTERFACE utilisateur et les threads de travail qu' Control.Invoke impose.
  • Il met la responsabilité de la mise à jour de l'UI thread sur le thread de l'INTERFACE utilisateur, où il devrait appartenir de toute façon.
  • Le thread d'INTERFACE utilisateur obtient de dicter quand et combien de fois la mise à jour devrait avoir lieu.
  • Il n'y a pas de risque de l'INTERFACE utilisateur de la pompe de message en cours de dépassement comme ce serait le cas avec le marshaling des techniques initié par le thread de travail.
  • Le thread de ne pas avoir à attendre une confirmation que la mise à jour a été effectuée avant de procéder à ses prochaines étapes (ie. vous obtenez plus de débit sur les deux l'INTERFACE utilisateur et les threads de travail).

Donc, envisager la création d'un System.Windows.Forms.Timer qui vérifie périodiquement pour que le texte soit affiché sur l' Control , au lieu de l'ouverture de la poussée de la thread de travail. Encore une fois, sans savoir exactement à vos exigences, je ne suis pas prêt à dire ce vraiment la direction qu'il faut aller, mais en plus de nombreux cas, il est mieux que l' Control.Invoke option.

Évidemment, cette approche élimine la nécessité de l' InvokedRequired vérifier entièrement. Passons sur le fait qu'il facilite tous les autres aspects de l'INTERFACE utilisateur / thread de travail de l'interaction.

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