Nettoyer le code avec InvokeRequired

je sais que lorsque vous manipulez les contrôles UI à partir de n'importe quel fil non-UI, vous devez canaliser vos appels vers le fil UI pour éviter les problèmes. Le consensus général est que vous devez utiliser test InvokeRequired, et si vrai, utiliser .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 la suivante: puis-je laisser de côté le test InvokeRequired et juste appeler Invoke, comme ceci:

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

y a-t-il un problème à faire ça? Si oui, y a-t-il une meilleure façon de conserver le test InvokeRequired tout en n'ayant pas à copier et coller Ce modèle partout?

44
demandé sur Mark Hurd 2010-10-06 19:26:23

8 réponses

que pensez-vous de ceci:

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

utilisez - le comme ceci:

private void UpdateSummary(string text)
{
    summary.InvokeIfRequired(s => { s.Text = text });
}
63
répondu John Gietzen 2015-09-10 21:31:24

appeler Invoke à partir du fil UI est quelque peu inefficace.

à la place, 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)

8
répondu SLaks 2010-10-06 15:29:45

j'ai lu les arguments de part et d'autre sur l'ajout d'une vérification logique pour savoir si l'invoke devrait être utilisé IFF quand ce n'est pas sur le thread D'UI et pas sur le thread D'UI lui-même. J'ai écrit une classe qui examine le temps d'exécution (par l'intermédiaire de Chronomètre ) de différentes méthodes pour obtenir une estimation approximative de l'efficacité d'une méthode plutôt qu'une autre.

les résultats peuvent surprendre certains d'entre vous (ces tests ont été effectués via le Forme.Affiché é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";
                    }
                 )
              );
           }
        }
     );

résultats sont les suivants:

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

ma conclusion est que vous pouvez invoquer en toute sécurité à tout moment, indépendamment du fait que vous êtes sur le fil UI ou un fil worker, sans frais généraux significatifs de boucle arrière via la pompe à message. Cependant, effectuer la plupart du travail sur le thread D'UI au lieu de faire de nombreux appels vers le thread D'UI (via Invoke() ) est avantageux et améliore considérablement l'efficacité.

7
répondu Michael 2010-10-06 18:32:54

je me rends compte qu'Il ya déjà une réponse qui est assez bien spot sur , mais je voulais aussi poster mon point de vue sur elle (que j'ai également posté ici ).

Mine est un peu différent en ce qu'il peut manipuler un peu plus sûr contrôles nuls et peut retourner des résultats lorsque nécessaire. Ces deux - là sont venus en pratique pour moi en essayant D'invoquer montrant une boîte de messagerie sur une forme de parent qui pourrait être null, et en retournant le Résultat du dialogue de montrer cette boîte à 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));
5
répondu Mark Rushakoff 2017-05-23 11:47:24

Je ne suis pas convaincu que Control.Invoke soit le meilleur choix pour mettre à jour L'UI. Je ne peux pas en être sûr dans votre cas parce que je ne connais pas les circonstances dans lesquelles UpdateSummary a appelé. Cependant, si vous l'appelez périodiquement comme un mécanisme pour afficher des informations de progrès (qui est l'impression que j'obtiens de l'extrait de code) alors il y a habituellement une meilleure option. Cette option est d'avoir le sondage UI thread pour le statut au lieu d'avoir le fil ouvrier le pousse.

les raisons pour lesquelles l'approche par sondage devrait être considérée dans ce cas sont les suivantes:

  • il brise le lien étroit entre L'UI et les fils de travailleur que Control.Invoke impose.
  • il place la responsabilité de mettre à jour le thread UI sur le thread UI où il devrait de toute façon appartenir.
  • le thread de L'UI dicte quand et combien de fois la mise à jour devrait dérouler.
  • il n'y a aucun risque de dépassement de la pompe à message de L'IU, comme ce serait le cas avec les techniques de triage initiées par le worker thread.
  • le thread worker n'a pas à attendre la confirmation que la mise à jour a été effectuée avant de passer aux étapes suivantes (c.-à-d.: vous obtenez plus de débit à la fois sur les fils UI et worker).

envisagez donc de créer un System.Windows.Forms.Timer qui périodiquement vérifie que le texte est affiché sur le Control au lieu de déclencher la poussée à partir du thread worker. Encore une fois, sans connaître vos exigences exactes Je ne suis pas disposé à dire ce certainement la direction que vous devez aller, mais dans la plupart beaucoup de cas il est mieux que l'option Control.Invoke .

évidemment, cette approche élimine entièrement la nécessité du contrôle InvokedRequired . Passons sur le fait qu'il simplie tous d'autres aspects de l'INTERFACE utilisateur / thread de travail de l'interaction.

3
répondu Brian Gideon 2010-10-06 16:29:37

mon approche préférée pour les contrôles de vue uniquement est d'avoir tous les états de contrôle encapsulés dans une classe qui peut être mise à jour sans jamais passer par des États incohérents (une façon simple de faire ceci est de mettre toutes les choses qui doivent être mises à jour ensemble dans une classe immuable, et de créer une nouvelle instance de la classe chaque fois qu'une mise à jour est nécessaire). Puis avoir une méthode qui sera entrelacés.Échanger un drapeau updateneededed et, s'il n'y a pas de mise à jour en attente mais IsHandleCreated est true, puis BeginInvoke la procédure de mise à jour. La procédure de mise à jour doit effacer le drapeau updateNeeded, comme la première chose qu'il fait, avant de faire des mises à jour (si quelqu'un essaie de mettre à jour le contrôle à ce moment-là, une autre requête sera initialisée). Notez que vous devez être prêt à attraper et avaler une exception (je pense IllegalOperation) si le contrôle est éliminé juste au moment où vous vous préparez à le mettre à jour.

soit dit en passant, si une commande n'a pas encore été reliée à un fil (en étant ajouté à une fenêtre visible, ou en ayant la fenêtre qu'il est sur devenir visible), il est légal de le mettre à jour directement mais pas légal D'utiliser BeginInvoke ou invoquer sur elle.

2
répondu supercat 2010-10-06 16:10:19

Je ne peux pas encore commenter, j'espère que quelqu'un verra cela et l'ajoutera à la réponse acceptée, qui est autrement spot sur.

control.Invoke(new Action(() => action(control))); doit se lire comme suit:

control.Invoke(new Action(() => action(control)), null);

comme écrit, la réponse acceptée ne sera pas compilée parce que ISynchronizeInvoke.Invoke() n'a pas une surcharge avec seulement un argument comme Control.Invoke() fait.

une autre chose est que l'usage pourrait être plus clair comme

summary.InvokeIfRequired(c => { summary.Text = text; }); plutôt que tel qu'écrit summary.InvokeIfRequired(c => { textBox.Text = text });

1
répondu Joseph A 2015-08-26 16:03:47

il est plus facile d'utiliser BackgroundWorker, si possible, pour rendre L'UI responsive et utiliser ReportProgress pour mettre à jour L'UI parce qu'il fonctionne sur le même thread que L'UI, donc vous n'aurez pas besoin D'InvokeRequired.

0
répondu sh_kamalh 2010-10-06 18:23:21