1529 votes

Comment mettre à jour l'interface graphique à partir d'un autre fil de discussion ?

Quelle est la façon la plus simple de mettre à jour une Label d'un autre Thread ?

  • J'ai un Form en cours thread1 et à partir de là, je commence un autre fil ( thread2 ).

  • Alors que thread2 traite certains fichiers, je voudrais mettre à jour un fichier Label sur le Form avec l'état actuel de thread2 Le travail de la Commission.

Comment pourrais-je faire ça ?

26 votes

La classe BackgroundWorker de .net 2.0+ n'est-elle pas prévue pour cela ? Elle prend en compte les threads de l'interface utilisateur. 1. Créer un BackgroundWorker 2. Ajoutez deux délégués (un pour le traitement et un pour l'achèvement).

14 votes

4 votes

Voir la réponse pour .NET 4.5 et C# 5.0 : stackoverflow.com/a/18033198/2042090

1167voto

Marc Gravell Points 482669

En le plus simple est une méthode anonyme transmise à Label.Invoke :

// Running on the worker thread
string newText = "abc";
form.Label.Invoke((MethodInvoker)delegate {
    // Running on the UI thread
    form.Label.Text = newText;
});
// Back on the worker thread

Notez que Invoke bloque l'exécution jusqu'à ce qu'elle soit terminée - il s'agit d'un code synchrone. La question ne porte pas sur le code asynchrone, mais il y a beaucoup de code asynchrone. contenu sur Stack Overflow sur l'écriture de code asynchrone lorsque vous souhaitez en savoir plus.

2 votes

Mais, alors votre fonction de traitement doit être une méthode membre de votre formulaire GUI ?

8 votes

Vu que le PO n'a pas mentionné de classe/instance sauf le formulaire, ce n'est pas un mauvais défaut...

42 votes

N'oubliez pas que le mot clé "this" fait référence à une classe "Control".

808voto

Ian Kemp Points 6408

Pour .NET 2.0, voici un petit bout de code que j'ai écrit et qui fait exactement ce que vous voulez, et qui fonctionne pour n'importe quelle propriété d'un objet de type Control :

private delegate void SetControlPropertyThreadSafeDelegate(
    Control control, 
    string propertyName, 
    object propertyValue);

public static void SetControlPropertyThreadSafe(
    Control control, 
    string propertyName, 
    object propertyValue)
{
  if (control.InvokeRequired)
  {
    control.Invoke(new SetControlPropertyThreadSafeDelegate               
    (SetControlPropertyThreadSafe), 
    new object[] { control, propertyName, propertyValue });
  }
  else
  {
    control.GetType().InvokeMember(
        propertyName, 
        BindingFlags.SetProperty, 
        null, 
        control, 
        new object[] { propertyValue });
  }
}

Disons-le comme ça :

// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);

Si vous utilisez .NET 3.0 ou une version plus récente, vous pouvez réécrire la méthode ci-dessus en tant que méthode d'extension de la méthode Control ce qui simplifierait l'appel à :

myLabel.SetPropertyThreadSafe("Text", status);

MISE À JOUR 05/10/2010 :

Pour .NET 3.0, vous devez utiliser ce code :

private delegate void SetPropertyThreadSafeDelegate<TResult>(
    Control @this, 
    Expression<Func<TResult>> property, 
    TResult value);

public static void SetPropertyThreadSafe<TResult>(
    this Control @this, 
    Expression<Func<TResult>> property, 
    TResult value)
{
  var propertyInfo = (property.Body as MemberExpression).Member 
      as PropertyInfo;

  if (propertyInfo == null ||
      !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
      @this.GetType().GetProperty(
          propertyInfo.Name, 
          propertyInfo.PropertyType) == null)
  {
    throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
  }

  if (@this.InvokeRequired)
  {
      @this.Invoke(new SetPropertyThreadSafeDelegate<TResult> 
      (SetPropertyThreadSafe), 
      new object[] { @this, property, value });
  }
  else
  {
      @this.GetType().InvokeMember(
          propertyInfo.Name, 
          BindingFlags.SetProperty, 
          null, 
          @this, 
          new object[] { value });
  }
}

qui utilise LINQ et les expressions lambda pour permettre une syntaxe beaucoup plus propre, plus simple et plus sûre :

// status has to be of type string or this will fail to compile
myLabel.SetPropertyThreadSafe(() => myLabel.Text, status);

Non seulement le nom de la propriété est désormais vérifié à la compilation, mais le type de la propriété l'est également, de sorte qu'il est impossible (par exemple) d'attribuer une valeur de type chaîne à une propriété de type booléen et de provoquer ainsi une exception à l'exécution.

Malheureusement, cela n'empêche pas les gens de faire des choses stupides, comme de passer dans un autre pays. Control de la propriété et de la valeur, de sorte que le texte suivant se compilera sans problème :

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);

C'est pourquoi j'ai ajouté des contrôles d'exécution afin de m'assurer que la propriété transmise appartient bien à l'élément Control sur laquelle la méthode est appelée. Ce n'est pas parfait, mais c'est beaucoup mieux que la version .NET 2.0.

Si quelqu'un a d'autres suggestions sur la façon d'améliorer ce code pour la sécurité à la compilation, merci de commenter !

3 votes

Il y a des cas où this.GetType() a la même valeur que propertyInfo.ReflectedType (par exemple LinkLabel sur WinForms). Je n'ai pas une grande expérience du C#, mais je pense que la condition d'exception devrait être : if (propertyInfo == null || (!@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) && @this.GetType() != propertyInfo.ReflectedType) || @this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)

9 votes

@lan peut le faire SetControlPropertyThreadSafe(myLabel, "Text", status) être appelé depuis un autre module, une autre classe ou un autre formulaire

0 votes

J'ai recommencé à l'utiliser parce que, ironiquement, BGWorker gèle le thread de l'interface utilisateur. En fait, je suis satisfait de la version v2... merci.....

441voto

Ryszard Dżegan Points 4079

Gestion des travaux de longue durée

Depuis .NET 4.5 et C# 5.0 vous devez utiliser Modèle asynchrone basé sur les tâches (TAP) ainsi que asynchrone - attendre mots-clés dans tous les domaines (y compris l'interface graphique) :

TAP est le modèle de conception asynchrone recommandé pour les nouveaux développements.

au lieu de Modèle de programmation asynchrone (APM) et Modèle asynchrone basé sur les événements (EAP) (ce dernier comprend le Classe BackgroundWorker ).

Alors, la solution recommandée pour les nouveaux développements est la suivante :

  1. Mise en œuvre asynchrone d'un gestionnaire d'événements (oui, c'est tout) :

    private async void Button_Clicked(object sender, EventArgs e)
    {
        var progress = new Progress<string>(s => label.Text = s);
        await Task.Factory.StartNew(() => SecondThreadConcern.LongWork(progress),
                                    TaskCreationOptions.LongRunning);
        label.Text = "completed";
    }
  2. Implémentation du second thread qui notifie le thread de l'interface utilisateur :

    class SecondThreadConcern
    {
        public static void LongWork(IProgress<string> progress)
        {
            // Perform a long running work...
            for (var i = 0; i < 10; i++)
            {
                Task.Delay(500).Wait();
                progress.Report(i.ToString());
            }
        }
    }

Notez ce qui suit :

  1. Code court et propre écrit de manière séquentielle sans callbacks et threads explicites.
  2. Tâche au lieu de Fil conducteur .
  3. asynchrone qui permet d'utiliser attendre qui, à son tour, empêche le gestionnaire d'événements d'atteindre l'état d'achèvement jusqu'à ce que la tâche soit terminée et, entre-temps, ne bloque pas le fil d'exécution de l'interface utilisateur.
  4. Classe de progrès (voir Interface IProgress ) qui prend en charge Séparation des préoccupations (SoC) et ne nécessite pas de répartiteur et d'invocation explicites. Il utilise le SynchronizationContext depuis son lieu de création (ici le thread de l'interface utilisateur).
  5. Options de création de tâches (TaskCreationOptions.LongRunning) qui indique de ne pas mettre en file d'attente la tâche dans ThreadPool .

Pour des exemples plus verbeux, voir : L'avenir du C# : Les bonnes choses arrivent à ceux qui "attendent". par Joseph Albahari .

Voir aussi Modèle d'exécution de l'interface utilisateur concept.

Traitement des exceptions

L'extrait ci-dessous est un exemple de la manière de gérer les exceptions et les boutons de basculement. Enabled pour empêcher les clics multiples pendant l'exécution en arrière-plan.

private async void Button_Click(object sender, EventArgs e)
{
    button.Enabled = false;

    try
    {
        var progress = new Progress<string>(s => button.Text = s);
        await Task.Run(() => SecondThreadConcern.FailingWork(progress));
        button.Text = "Completed";
    }
    catch(Exception exception)
    {
        button.Text = "Failed: " + exception.Message;
    }

    button.Enabled = true;
}

class SecondThreadConcern
{
    public static void FailingWork(IProgress<string> progress)
    {
        progress.Report("I will fail in...");
        Task.Delay(500).Wait();

        for (var i = 0; i < 3; i++)
        {
            progress.Report((3 - i).ToString());
            Task.Delay(500).Wait();
        }

        throw new Exception("Oops...");
    }
}

2 votes

Si SecondThreadConcern.LongWork() lance une exception, peut-elle être attrapée par le fil de l'interface utilisateur ? Cet article est excellent.

2 votes

J'ai ajouté une section supplémentaire à la réponse pour répondre à vos besoins. Salutations.

3 votes

Le site Classe ExceptionDispatchInfo est responsable de ce miracle qui consiste à relancer une exception sur le thread de l'interface utilisateur dans le modèle async-await.

270voto

Zaid Masud Points 4536

Variation de Marc Gravell le plus simple solution pour .NET 4 :

control.Invoke((MethodInvoker) (() => control.Text = "new text"));

Ou utilisez plutôt un délégué d'action :

control.Invoke(new Action(() => control.Text = "new text"));

Voir ici pour une comparaison des deux : MethodInvoker vs Action pour Control.BeginInvoke

1 votes

Qu'est-ce que le "contrôle" dans cet exemple ? Mon contrôle d'interface utilisateur ? J'essaie d'implémenter ceci dans WPF sur un contrôle d'étiquette, et Invoke n'est pas un membre de mon étiquette.

0 votes

De quoi s'agit-il ? méthode d'extension comme @styxriver stackoverflow.com/a/3588137/206730 ?

0 votes

Déclarez 'Action y;' à l'intérieur de la classe ou de la méthode qui modifie la propriété text et mettez à jour le texte avec ce morceau de code 'yourcontrol.Invoke(y=() => yourcontrol.Text = "new text");'.

145voto

StyxRiver Points 1085

Méthode d'extension "Feu et oublie" pour .NET 3.5+.

using System;
using System.Windows.Forms;

public static class ControlExtensions
{
    /// <summary>
    /// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.
    /// </summary>
    /// <param name="control"></param>
    /// <param name="code"></param>
    public static void UIThread(this Control @this, Action code)
    {
        if (@this.InvokeRequired)
        {
            @this.BeginInvoke(code);
        }
        else
        {
            code.Invoke();
        }
    }
}

On peut l'appeler en utilisant la ligne de code suivante :

this.UIThread(() => this.myLabel.Text = "Text Goes Here");

5 votes

Quel est l'intérêt de l'utilisation de @this ? Le terme "contrôle" ne serait-il pas équivalent ? L'utilisation de @this présente-t-elle des avantages ?

16 votes

@jeromeyers - Le @this est simplement le nom de la variable, dans ce cas la référence au contrôle courant qui appelle l'extension. Vous pouvez la renommer en source, ou ce qui vous convient le mieux. J'utilise @this parce qu'il se réfère à "ce contrôle" qui appelle l'extension et est cohérent (dans ma tête, au moins) avec l'utilisation du mot-clé "this" dans le code normal (sans extension).

2 votes

C'est génial, facile et pour moi la meilleure solution. Vous pourriez inclure tout le travail que vous avez à faire dans le thread de l'interface utilisateur. Exemple : this.UIThread(() => { txtMessage.Text = message ; listBox1.Items.Add(message) ; }) ;

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