c# invoke thread




Comment puis-je mettre à jour l'interface graphique d'un autre thread? (20)

Quel est le moyen le plus simple de mettre à jour un Label depuis un autre thread?

J'ai un Form sur thread1 , et à partir de là je commence un autre thread ( thread2 ). Alors que thread2 traite certains fichiers, je voudrais mettre à jour une Label sur le Form avec l'état actuel du travail de thread2 .

Comment puis je faire ça?


Manipuler un long travail

Depuis .NET 4.5 et C # 5.0, vous devez utiliser TAP (Asynchronous Pattern) basé sur les tâches avec les mots-clés async - await dans toutes les zones (y compris l'interface graphique):

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

à la place du modèle de programmation asynchrone (APM) et du modèle asynchrone événementiel (EAP) (ce dernier inclut la classe BackgroundWorker ).

Ensuite, la solution recommandée pour un nouveau développement est:

  1. Implémentation 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 deuxième thread qui notifie le thread UI:

    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. Un code court et propre écrit de manière séquentielle sans callback ni thread explicite.
  2. Task au lieu de Thread .
  3. mot-clé async , qui permet d'utiliser l' await ce qui empêche le gestionnaire d'événements d'atteindre l'état d'achèvement jusqu'à la fin de la tâche et ne bloque pas le thread de l'interface utilisateur.
  4. Classe de progression (voir l' interface IProgress ) qui prend en charge le principe de conception de séparation des problèmes (SoC) et ne nécessite pas de répartiteur et d'invocation explicites. Il utilise le SynchronizationContext actuel à partir de son lieu de création (ici le thread UI).
  5. TaskCreationOptions.LongRunning qui indique de ne pas mettre en file d'attente la tâche dans ThreadPool .

Pour des exemples plus détaillés, voir: L'avenir de C #: Les bonnes choses viennent à ceux qui «attendent» de Joseph Albahari .

Voir aussi à propos du concept de modèle de filetage UI .

Gestion des exceptions

L'extrait ci-dessous est un exemple de la façon de gérer les exceptions et de basculer la propriété Enabled du bouton pour empêcher plusieurs clics lors de 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...");
    }
}

Aucun des éléments Invoke dans les réponses précédentes n'est nécessaire.

Vous devez regarder WindowsFormsSynchronizationContext:

// In the main thread
WindowsFormsSynchronizationContext mUiContext = new WindowsFormsSynchronizationContext();

...

// In some non-UI Thread

// Causes an update in the GUI thread.
mUiContext.Post(UpdateGUI, userData);

...

void UpdateGUI(object userData)
{
    // Update your GUI controls here
}

Ceci dans ma variante C # 3.0 de la solution de Ian Kemp:

public static void SetPropertyInGuiThread<C,V>(this C control, Expression<Func<C, V>> property, V value) where C : Control
{
    var memberExpression = property.Body as MemberExpression;
    if (memberExpression == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    var propertyInfo = memberExpression.Member as PropertyInfo;
    if (propertyInfo == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    if (control.InvokeRequired)
        control.Invoke(
            (Action<C, Expression<Func<C, V>>, V>)SetPropertyInGuiThread,
            new object[] { control, property, value }
        );
    else
        propertyInfo.SetValue(control, value, null);
}

Vous l'appelez comme ceci:

myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
  1. Il ajoute null-checking au résultat de "as MemberExpression".
  2. Il améliore la sécurité de type statique.

Sinon, l'original est une très belle solution.


Celui-ci est similaire à la solution ci-dessus utilisant .NET Framework 3.0, mais il a résolu le problème du support de sécurité à la compilation .

public  static class ControlExtension
{
    delegate void SetPropertyValueHandler<TResult>(Control souce, Expression<Func<Control, TResult>> selector, TResult value);

    public static void SetPropertyValue<TResult>(this Control source, Expression<Func<Control, TResult>> selector, TResult value)
    {
        if (source.InvokeRequired)
        {
            var del = new SetPropertyValueHandler<TResult>(SetPropertyValue);
            source.Invoke(del, new object[]{ source, selector, value});
        }
        else
        {
            var propInfo = ((MemberExpression)selector.Body).Member as PropertyInfo;
            propInfo.SetValue(source, value, null);
        }
    }
}

Utiliser:

this.lblTimeDisplay.SetPropertyValue(a => a.Text, "some string");
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, false);

Le compilateur échouera si l'utilisateur passe le mauvais type de données.

this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");

Ignorer et ignorer la méthode d'extension 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();
        }
    }
}

Cela peut être appelé en utilisant la ligne de code suivante:

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

La grande majorité des réponses utilisent Control.Invoke qui est une condition de Control.Invoke attente . Par exemple, considérez la réponse acceptée:

string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate { 
    someLabel.Text = newText; // runs on UI thread
});

Si l'utilisateur ferme le formulaire juste avant cela. this.Invoke est appelé (rappelez-vous, this s'agit de l'objet Form ), une ObjectDisposedException sera probablement déclenchée.

La solution consiste à utiliser SynchronizationContext , plus précisément SynchronizationContext.Current comme le suggère hamilton.danielb (d'autres réponses reposent sur des implémentations SynchronizationContext spécifiques, ce qui est totalement inutile). Je modifierais légèrement son code pour utiliser SynchronizationContext.Post plutôt que SynchronizationContext.Send (car il n'est généralement pas nécessaire que le thread de travail attende):

public partial class MyForm : Form
{
    private readonly SynchronizationContext _context;
    public MyForm()
    {
        _context = SynchronizationContext.Current
        ...
    }

    private MethodOnOtherThread()
    {
         ...
         _context.Post(status => someLabel.Text = newText,null);
    }
}

Notez que sur .NET 4.0 et plus, vous devriez vraiment utiliser des tâches pour les opérations asynchrones. Voir n-san's réponse n-san's pour l'approche basée sur les tâches équivalente (en utilisant TaskScheduler.FromCurrentSynchronizationContext ).

Enfin, sur .NET 4.5 et plus, vous pouvez également utiliser Progress<T> (qui capture essentiellement SynchronizationContext.Current lors de sa création) comme démontré par Ryszard Dżegan pour les cas où l'opération de longue durée doit exécuter du code UI tout en continuant à fonctionner.


Le code Threading est souvent buggé et toujours difficile à tester. Vous n'avez pas besoin d'écrire du code de thread pour mettre à jour l'interface utilisateur à partir d'une tâche en arrière-plan. Utilisez simplement la classe BackgroundWorker pour exécuter la tâche et sa méthode ReportProgress pour mettre à jour l'interface utilisateur. Habituellement, vous venez de signaler un pourcentage complet, mais il y a une autre surcharge qui inclut un objet d'état. Voici un exemple qui signale simplement un objet chaîne:

    private void button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.WorkerReportsProgress = true;
        backgroundWorker1.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "A");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "B");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "C");
    }

    private void backgroundWorker1_ProgressChanged(
        object sender, 
        ProgressChangedEventArgs e)
    {
        label1.Text = e.UserState.ToString();
    }

C'est très bien si vous voulez toujours mettre à jour le même champ. Si vous avez des mises à jour plus compliquées à faire, vous pouvez définir une classe pour représenter l'état de l'interface utilisateur et la passer à la méthode ReportProgress.

Une dernière chose, assurez-vous de définir l'indicateur WorkerReportsProgress , ou la méthode ReportProgress sera complètement ignorée.


Le moyen le plus simple est une méthode anonyme passée dans 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'à la fin - c'est un code synchrone. La question ne concerne pas le code asynchrone, mais contient beaucoup de contenu sur l' écriture de code asynchrone lorsque vous voulez en savoir plus.


Pour de nombreuses raisons, c'est aussi simple que ceci:

public delegate void serviceGUIDelegate();
private void updateGUI()
{
  this.Invoke(new serviceGUIDelegate(serviceGUI));
}

"serviceGUI ()" est une méthode de niveau GUI au sein du formulaire (this) qui peut changer autant de contrôles que vous le souhaitez. Appelez "updateGUI ()" à partir de l'autre thread. Des paramètres peuvent être ajoutés pour transmettre des valeurs, ou (probablement plus rapide) utiliser des variables de portée de classe avec des verrous sur eux si nécessaire, s'il y a une possibilité de conflit entre les threads qui y accèdent, ce qui pourrait causer de l'instabilité. Utilisez BeginInvoke au lieu de Invoke si le thread non-GUI est critique dans le temps (en gardant à l'esprit l'avertissement de Brian Gideon).


Quand j'ai rencontré le même problème, j'ai cherché de l'aide auprès de Google, mais plutôt que de me donner une solution simple, cela m'a plus MethodInvoker en donnant des exemples de MethodInvoker et de bla bla bla. J'ai donc décidé de le résoudre par moi-même. Voici ma solution:

Faites un délégué comme ceci:

Public delegate void LabelDelegate(string s);

void Updatelabel(string text)
{
   if (label.InvokeRequired)
   {
       LabelDelegate LDEL = new LabelDelegate(Updatelabel);
       label.Invoke(LDEL, text);
   }
   else
       label.Text = text
}

You can call this function in a new thread like this

Thread th = new Thread(() => Updatelabel("Hello World"));
th.start();

Don't be confused with Thread(() => .....) . I use an anonymous function or lambda expression when I work on a thread. To reduce the lines of code you can use the ThreadStart(..) method too which I am not supposed to explain here.



Vous devrez appeler la méthode sur le thread graphique. Vous pouvez le faire en appelant Control.Invoke.

Par exemple:

delegate void UpdateLabelDelegate (string message);

void UpdateLabel (string message)
{
    if (InvokeRequired)
    {
         Invoke (new UpdateLabelDelegate (UpdateLabel), message);
         return;
    }

    MyLabelControl.Text = message;
}

Create a class variable:

SynchronizationContext _context;

Set it in the constructor that creates your UI:

var _context = SynchronizationContext.Current;

When you want to update the label:

_context.Send(status =>{
    // UPDATE LABEL
}, null);

Even if the operation is time-consuming (thread.sleep in my example) - This code will NOT lock your UI:

 private void button1_Click(object sender, EventArgs e)
 {

      Thread t = new Thread(new ThreadStart(ThreadJob));
      t.IsBackground = true;
      t.Start();         
 }

 private void ThreadJob()
 {
     string newValue= "Hi";
     Thread.Sleep(2000); 

     this.Invoke((MethodInvoker)delegate
     {
         label1.Text = newValue; 
     });
 }

I just read the answers and this appears to be a very hot topic. I'm currently using .NET 3.5 SP1 and Windows Forms.

The well-known formula greatly described in the previous answers that makes use of the InvokeRequired property covers most of the cases, but not the entire pool.

What if the Handle has not been created yet?

The InvokeRequired property, as described here (Control.InvokeRequired Property reference to MSDN) returns true if the call was made from a thread that is not the GUI thread, false either if the call was made from the GUI thread, or if the Handle was not created yet.

You can come across an exception if you want to have a modal form shown and updated by another thread. Because you want that form shown modally, you could do the following:

private MyForm _gui;

public void StartToDoThings()
{
    _gui = new MyForm();
    Thread thread = new Thread(SomeDelegate);
    thread.Start();
    _gui.ShowDialog();
}

And the delegate can update a Label on the GUI:

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.InvokeRequired)
        _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
    else
        _gui.Label1.Text = "Done!";
}

This can cause an InvalidOperationException if the operations before the label's update "take less time" (read it and interpret it as a simplification) than the time it takes for the GUI thread to create the Form 's Handle . This happens within the ShowDialog() method.

You should also check for the Handle like this:

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.IsHandleCreated)  //  <---- ADDED
        if(_gui.InvokeRequired)
            _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
        else
            _gui.Label1.Text = "Done!";
}

You can handle the operation to perform if the Handle has not been created yet: You can just ignore the GUI update (like shown in the code above) or you can wait (more risky). This should answer the question.

Optional stuff: Personally I came up coding the following:

public class ThreadSafeGuiCommand
{
  private const int SLEEPING_STEP = 100;
  private readonly int _totalTimeout;
  private int _timeout;

  public ThreadSafeGuiCommand(int totalTimeout)
  {
    _totalTimeout = totalTimeout;
  }

  public void Execute(Form form, Action guiCommand)
  {
    _timeout = _totalTimeout;
    while (!form.IsHandleCreated)
    {
      if (_timeout <= 0) return;

      Thread.Sleep(SLEEPING_STEP);
      _timeout -= SLEEPING_STEP;
    }

    if (form.InvokeRequired)
      form.Invoke(guiCommand);
    else
      guiCommand();
  }
}

I feed my forms that get updated by another thread with an instance of this ThreadSafeGuiCommand , and I define methods that update the GUI (in my Form) like this:

public void SetLabeTextTo(string value)
{
  _threadSafeGuiCommand.Execute(this, delegate { Label1.Text = value; });
}

In this way I'm quite sure that I will have my GUI updated whatever thread will make the call, optionally waiting for a well-defined amount of time (the timeout).


I wanted to add a warning because I noticed that some of the simple solutions omit the InvokeRequired check.

I noticed that if your code executes before the window handle of the control has been created (eg before the form is shown), Invoke throws an exception. So I recommend always checking on InvokeRequired before calling Invoke or BeginInvoke .


Simply use something like this:

 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });

The easiest way I think:

   void Update()
   {
       BeginInvoke((Action)delegate()
       {
           //do your update
       });
   }

You may use the already-existing delegate Action :

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}

You must use invoke and delegate

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });




user-interface