Comment mettre à jour l'interface graphique à partir d'un autre thread?

Quelle est la manière la plus simple de mettre à jour un Label à partir d'un autre thread?

j'ai un Form sur thread1 , et de là je commence un autre fil ( thread2 ). Alors que thread2 traite certains fichiers, je voudrais mettre à jour un Label sur le Form avec l'état actuel de thread2 de l 'Travail.

Comment faire?

1176
demandé sur Uwe Keim 2009-03-19 12:37:40

30 réponses

pour .NET 2.0, voici un joli morceau de code que j'ai écrit qui fait exactement ce que vous voulez, et fonctionne pour n'importe quelle propriété sur un 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 });
  }
}

appelez ça comme ça:

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

si vous utilisez .NET 3.0 ou supérieur, vous pouvez réécrire la méthode ci-dessus comme une extension de la classe Control , ce qui simplifierait l'appel à:

myLabel.SetPropertyThreadSafe("Text", status);

mettre à JOUR le 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 des expressions LINQ et lambda pour permettre une syntaxe beaucoup plus propre, plus simple et plus sûre:

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

non seulement le nom de la propriété est maintenant vérifié au moment de la compilation, mais le type de la propriété est aussi bien, il est donc impossible (par exemple) d'attribuer une valeur de chaîne à une propriété booléenne, et donc de provoquer une exception d'exécution.

Malheureusement, cela n'empêche personne de faire des choses stupides telles que passer dans la propriété et la valeur d'un autre Control , de sorte que ce qui suit sera compilé avec plaisir:

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

J'ai donc ajouté les vérifications d'exécution pour s'assurer que la propriété transmise appartient bien à la Control sur laquelle la méthode est appelée. Pas parfait, mais encore beaucoup mieux que la .NET version 2.0.

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

701
répondu Ian Kemp 2015-03-03 00:52:09

la la manière la plus simple est une méthode anonyme 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--c'est du code synchrone. La question ne pose pas de questions sur le code asynchrone, mais il y a beaucoup de contenu sur le débordement de la pile sur l'écriture de code asynchrone quand vous voulez en savoir plus.

956
répondu Marc Gravell 2017-09-16 09:20:29

manipulation de travail de longue durée

Depuis .NET 4.5, C# 5.0 vous devez utiliser basée sur les Tâches du Modèle Asynchrone (TAP) avec async - attendent mots-clés dans tous les domaines (y compris l'interface graphique):

ROBINET " est recommandée asynchrone modèle de conception pour le développement de nouvelles

au lieu de asynchronous Programming Model (APM) et Event-based Asynchronous Pattern (EAP) (ce dernier comprend la classe BackgroundWorker ).

alors, la solution recommandée pour le nouveau développement est:

  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. la mise en Œuvre de la deuxième thread qui avertit le thread d'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());
            }
        }
    }
    

Avis la suivante:

  1. code court et clair écrit de manière séquentielle sans callbacks et threads explicites.
  2. tâche au lieu de Thread .
  3. async mot-clé, qui permet d'utiliser attendre qui à son tour empêcher le gestionnaire d'événement d'atteindre l'état d'achèvement jusqu'à ce que la tâche terminée et en attendant ne bloque pas le fil UI.
  4. classe de progression (voir Iprogress Interface ) supportant séparation des préoccupations (SoC) principe de conception et ne nécessite pas de régulateur explicite et d'invocation. Il utilise le courant SynchronizationContext de son lieu de création (ici le thread UI).
  5. TaskCreationOptions.LongRunning qui suggère de ne pas faire la file d'attente de la tâche dans ThreadPool .

pour des exemples plus parlants voir: L'avenir de C#: de bonnes choses viennent à ceux qui "attendent" par Joseph Albahari .

Voir aussi à propos de UI Threading Model concept.

exceptions concernant la manutention

L'extrait ci-dessous est un exemple de la façon de gérer les exceptions et la propriété Enabled de bouton à bascule pour empêcher plusieurs clics pendant l'exécution de l'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...");
    }
}
344
répondu Ryszard Dżegan 2017-05-23 11:55:02

Variation de Marc Gravell's le plus simple solution for .NET 4:

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

ou utiliser Action delegate à la place:

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

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

200
répondu Zaid Masud 2017-05-23 12:34:51

Fire and forget extension method for .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");
117
répondu StyxRiver 2010-08-27 21:10:32

C'est la façon classique de faire:

using System;
using System.Windows.Forms;
using System.Threading;

namespace Test
{
    public partial class UIThread : Form
    {
        Worker worker;

        Thread workerThread;

        public UIThread()
        {
            InitializeComponent();

            worker = new Worker();
            worker.ProgressChanged += new EventHandler<ProgressChangedArgs>(OnWorkerProgressChanged);
            workerThread = new Thread(new ThreadStart(worker.StartWork));
            workerThread.Start();
        }

        private void OnWorkerProgressChanged(object sender, ProgressChangedArgs e)
        {
            // Cross thread - so you don't get the cross-threading exception
            if (this.InvokeRequired)
            {
                this.BeginInvoke((MethodInvoker)delegate
                {
                    OnWorkerProgressChanged(sender, e);
                });
                return;
            }

            // Change control
            this.label1.Text = e.Progress;
        }
    }

    public class Worker
    {
        public event EventHandler<ProgressChangedArgs> ProgressChanged;

        protected void OnProgressChanged(ProgressChangedArgs e)
        {
            if(ProgressChanged!=null)
            {
                ProgressChanged(this,e);
            }
        }

        public void StartWork()
        {
            Thread.Sleep(100);
            OnProgressChanged(new ProgressChangedArgs("Progress Changed"));
            Thread.Sleep(100);
        }
    }


    public class ProgressChangedArgs : EventArgs
    {
        public string Progress {get;private set;}
        public ProgressChangedArgs(string progress)
        {
            Progress = progress;
        }
    }
}

votre fil ouvrier a un événement. Votre UI thread démarre un autre thread pour faire le travail et accroche cet événement worker de sorte que vous puissiez afficher l'état du worker thread.

ensuite, dans L'interface utilisateur, vous devez croiser les fils pour changer le contrôle réel... comme une étiquette ou une barre de progression.

59
répondu Hath 2017-03-26 09:45:55

la solution simple est d'utiliser Control.Invoke .

void DoSomething()
{
    if (InvokeRequired) {
        Invoke(new MethodInvoker(updateGUI));
    } else {
        // Do Something
        updateGUI();
    }
}

void updateGUI() {
    // update gui here
}
52
répondu OregonGhost 2015-12-19 10:02:10

code filetage est souvent buggy et toujours difficile à tester. Vous n'avez pas besoin d'écrire du code threading pour mettre à jour l'interface utilisateur à partir d'une tâche de fond. Il suffit d'utiliser la classe BackgroundWorker pour exécuter la tâche et sa méthode ReportProgress pour mettre à jour l'interface utilisateur. D'habitude, vous déclarez juste un pourcentage complet, mais il y a une autre surcharge qui inclut un objet d'état. Voici un exemple qui ne fait que rapporter un objet string:

    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 mettre le drapeau WorkerReportsProgress , ou la méthode ReportProgress sera complètement ignorée.

41
répondu Don Kirkby 2012-09-22 03:59:50

la grande majorité des réponses utilisent Control.Invoke qui est une condition de race en attente d'arriver . 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 que this.Invoke soit appelé (rappelez-vous, this est l'objet Form ), un ObjectDisposedException sera probablement tiré.

la solution est d'utiliser SynchronizationContext , spécifiquement SynchronizationContext.Current comme Hamilton.danielb suggère (d'autres réponses reposent sur des implémentations spécifiques SynchronizationContext qui sont complètement inutiles). Je modifierais légèrement son code pour utiliser SynchronizationContext.Post au lieu de SynchronizationContext.Send cependant (comme il n'y a généralement pas besoin pour le fil ouvrier d'attendre):

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 des opérations asynchrones. Voir n-san répondre pour l'équivalent de la tâche approche (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 le code UI tout en travaillant.

32
répondu Ohad Schneider 2017-05-23 12:34:51

vous devez vous assurer que la mise à jour se produit sur le thread correct; le thread UI.

pour ce faire, vous aurez pour Appeler le gestionnaire d'événements au lieu de l'appeler directement.

Vous pouvez le faire en augmentant votre événement comme celui-ci:

(le code est tapé ici hors de ma tête, donc je n'ai pas vérifié la syntaxe correcte, etc. mais il devrait vous aller.)

if( MyEvent != null )
{
   Delegate[] eventHandlers = MyEvent.GetInvocationList();

   foreach( Delegate d in eventHandlers )
   {
      // Check whether the target of the delegate implements 
      // ISynchronizeInvoke (Winforms controls do), and see
      // if a context-switch is required.
      ISynchronizeInvoke target = d.Target as ISynchronizeInvoke;

      if( target != null && target.InvokeRequired )
      {
         target.Invoke (d, ... );
      }
      else
      {
          d.DynamicInvoke ( ... );
      }
   }
}

noter que le le code ci-dessus ne fonctionnera pas pour les projets du FPF, puisque les contrôles du FPF ne mettent pas en œuvre l'interface ISynchronizeInvoke .

afin de vous assurer que le code ci-dessus fonctionne avec Windows Forms et WPF, et toutes les autres plateformes, vous pouvez jeter un oeil aux classes AsyncOperation , AsyncOperationManager et SynchronizationContext .

afin de soulever facilement des événements de cette façon, j'ai créé une méthode d'extension, qui me permet de simplifier soulever un événement en appelant simplement:

MyEvent.Raise(this, EventArgs.Empty);

bien sûr, vous pouvez également utiliser la classe BackGroundWorker, qui abstraira cette matière pour vous.

30
répondu Frederik Gheysels 2017-09-16 10:05:49

vous aurez besoin D'invoquer la méthode sur le fil GUI. Vous pouvez le faire en appelant le Contrôle.Invoquer.

par exemple:

delegate void UpdateLabelDelegate (string message);

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

    MyLabelControl.Text = message;
}
26
répondu Kieron 2009-03-19 09:47:27

en raison de la trivialité du scénario, j'aurais en fait le sondage de L'UI thread pour le statut. Je pense que vous trouverez qu'il peut être très élégant.

public class MyForm : Form
{
  private volatile string m_Text = "";
  private System.Timers.Timer m_Timer;

  private MyForm()
  {
    m_Timer = new System.Timers.Timer();
    m_Timer.SynchronizingObject = this;
    m_Timer.Interval = 1000;
    m_Timer.Elapsed += (s, a) => { MyProgressLabel.Text = m_Text; };
    m_Timer.Start();
    var thread = new Thread(WorkerThread);
    thread.Start();
  }

  private void WorkerThread()
  {
    while (...)
    {
      // Periodically publish progress information.
      m_Text = "Still working...";
    }
  }
}

l'approche permet d'éviter l'opération de triage requise lorsque l'on utilise les méthodes ISynchronizeInvoke.Invoke et ISynchronizeInvoke.BeginInvoke . Il n'y a rien de mal à utiliser cette technique, mais il y a quelques mises en garde dont vous devez être conscient.

  • assurez-vous de ne pas appelez BeginInvoke trop souvent ou il pourrait dépasser la pompe à message.
  • appelant Invoke sur le fil worker est un appel de blocage. Il va temporairement arrêter le travail en cours dans ce fil.

la stratégie que je propose dans cette réponse inverse les rôles de communication des fils. Au lieu du worker thread poussant les données les sondages de UI thread pour elle. Ce modèle commun utilisé dans de nombreux scénarios. Car tous vous êtes désireux pour faire est d'Afficher des informations de progrès du thread de travailleur puis je pense que vous trouverez que cette solution est une excellente alternative à la solution de marshaling. Il présente les avantages suivants.

  • les liens entre L'assurance-chômage et les travailleurs demeurent lâchement couplés, contrairement à l'approche Control.Invoke ou Control.BeginInvoke qui les couple étroitement.
  • le fil UI n'empêchera pas la progression du fil worker.
  • le le worker thread ne peut pas dominer le temps que le UI thread passe à mettre à jour.
  • les intervalles entre les opérations de L'UI et du worker threads peuvent rester indépendants.
  • le thread worker ne peut pas dépasser la pompe de message du thread UI.
  • le thread de L'UI dicte quand et combien de fois L'UI est mis à jour.
23
répondu Brian Gideon 2017-03-26 09:48:03

aucune des réponses précédentes N'est nécessaire.

Vous avez besoin de 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
}
23
répondu Jon H 2017-03-26 10:06:14

pour plusieurs raisons, c'est aussi simple que cela:

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

"serviceGUI ()" est une méthode de niveau GUI dans la forme (ceci) qui peut changer autant de contrôles que vous voulez. Appeler "updateGUI ()" à partir de l'autre thread. Les paramètres peuvent être ajoutés aux valeurs pass, ou (probablement plus rapidement) utiliser des variables de portée de classe avec des verrous sur elles comme requis s'il y a une possibilité d'un conflit entre les threads y accédant qui pourrait causer de l'instabilité. Utilisez BeginInvoke au lieu D'invoquer si le fil non-GUI est critique pour le temps (en gardant à l'esprit L'avertissement de Brian Gideon).

19
répondu Frankg 2010-08-26 03:17:44

dans mon C# 3.0 variation de Ian Kemp solution:

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 appelez ça comme ça:

myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
  1. il ajoute la vérification nulle au résultat de "As MemberExpression".
  2. il améliore la sécurité statique.

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

19
répondu Rotaerk 2011-09-15 20:40:57

celui-ci est similaire à la solution ci-dessus en utilisant .net Framework 3.0, mais il a résolu le problème de compiler-time safety support .

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");
19
répondu Francis 2017-03-26 09:56:51

Salvete! Ayant cherché cette question, j'ai trouvé les réponses par FrankG et Oregon Ghost pour être le plus facile plus utile pour moi. Maintenant, je code dans Visual Basic et j'ai lancé ce fragment à travers un convertisseur, donc je ne suis pas sûr de savoir comment ça se termine.

j'ai une forme de dialogue appelée form_Diagnostics, qui a une boîte de dialogue riche, appelée updateDiagWindow, que j'utilise comme une sorte d'affichage de journalisation. J'avais besoin de pouvoir mettre à jour son texte à partir de tous les threads. Les lignes supplémentaires permettent à la fenêtre pour faire défiler automatiquement les plus récentes lignes.

et donc, je peux maintenant mettre à jour l'affichage avec une ligne, de n'importe où dans le programme entier de la manière dont vous pensez qu'il fonctionnerait sans aucun filetage:

  form_Diagnostics.updateDiagWindow(whatmessage);

code principal (mettez ce code dans le code de classe de votre formulaire):

#region "---------Update Diag Window Text------------------------------------"
// This sub allows the diag window to be updated by all threads
public void updateDiagWindow(string whatmessage)
{
    var _with1 = diagwindow;
    if (_with1.InvokeRequired) {
        _with1.Invoke(new UpdateDiagDelegate(UpdateDiag), whatmessage);
    } else {
        UpdateDiag(whatmessage);
    }
}
// This next line makes the private UpdateDiagWindow available to all threads
private delegate void UpdateDiagDelegate(string whatmessage);
private void UpdateDiag(string whatmessage)
{
    var _with2 = diagwindow;
    _with2.appendtext(whatmessage);
    _with2.SelectionStart = _with2.Text.Length;
    _with2.ScrollToCaret();
}
#endregion
19
répondu bgmCoder 2017-03-26 09:58:47
Label lblText; //initialized elsewhere

void AssignLabel(string text)
{
   if (InvokeRequired)
   {
      BeginInvoke((Action<string>)AssignLabel, text);
      return;
   }

   lblText.Text = text;           
}

notez que BeginInvoke() est préférable à Invoke() parce qu'il est moins probable de causer des blocages (cependant, ce n'est pas un problème ici quand il suffit d'assigner du texte à une étiquette):

lorsque vous utilisez Invoke() , vous attendez que la méthode revienne. Maintenant, il se peut que vous fassiez quelque chose dans le code invoqué qui devra attendre le thread, ce qui n'est peut-être pas immédiatement évident s'il est enterré dans certaines fonctions que vous appelez, qui se peut se produire indirectement par l'intermédiaire de gestionnaires d'événements. Donc vous attendriez le fil, le fil vous attendrait et vous êtes bloqués.

cela a en fait causé la suspension de certains de nos logiciels libérés. Il était assez facile de réparer en remplaçant Invoke() par BeginInvoke() . Sauf si vous avez besoin d'une opération synchrone, ce qui peut être le cas si vous avez besoin d'une valeur de retour, utilisez BeginInvoke() .

18
répondu ILoveFortran 2017-03-08 12:44:54

lorsque j'ai rencontré le même problème, J'ai demandé l'aide de Google, mais plutôt que de me donner une solution simple, il m'a plus confus en donnant des exemples de MethodInvoker et bla bla bla. J'ai donc décidé de le résoudre sur mon propre. 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
}

Vous pouvez appeler cette fonction dans un nouveau thread comme celui-ci

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

ne pas confondre avec Thread(() => .....) . J'utilise un anonyme fonction ou expression lambda quand je travaille sur un fil. Pour réduire les lignes de code, vous pouvez aussi utiliser la méthode ThreadStart(..) que je ne suis pas censé expliquer ici.

16
répondu ahmar 2017-04-24 07:23:27

utilisez simplement quelque chose comme ceci:

 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });
14
répondu Hassan Shouman 2016-01-11 19:47:30

vous pouvez utiliser le délégué déjà existant Action :

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}
13
répondu Embedd_Khurja 2017-03-26 10:01:11

essayez de rafraîchir l'étiquette en utilisant ce

public static class ExtensionMethods
{
    private static Action EmptyDelegate = delegate() { };

    public static void Refresh(this UIElement uiElement)
    {
        uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }
}
12
répondu Ivaylo Slavov 2014-04-17 09:56:54

ma version est d'insérer une ligne de "mantra" récursif:

(pas d'arguments:

    void Aaaaaaa()
    {
        if (InvokeRequired) { Invoke(new Action(Aaaaaaa)); return; } //1 line of mantra

        // Your code!
    }

pour une fonction qui a des arguments:

    void Bbb(int x, string text)
    {
        if (InvokeRequired) { Invoke(new Action<int, string>(Bbb), new[] { x, text }); return; }
        // Your code!
    }

C'est-à-dire .


certains arguments : Habituellement, il est mauvais pour la lisibilité du code de mettre {} après une if () déclaration dans une ligne. Mais dans ce cas, c'est le même "mantra"de routine. Il ne casse pas la lisibilité du code si cette méthode est cohérente sur le projet. Et il sauve votre code de la salissure (une ligne de code au lieu de cinq).

Comme vous le voyez if(InvokeRequired) {something long} vous savez "cette fonction est possible d'appeler à partir d'un autre thread".

12
répondu MajesticRa 2017-03-26 10:00:26

vous devez utiliser invoke et delegate

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
11
répondu A. Zalonis 2013-09-10 13:55:34

créer une variable de classe:

SynchronizationContext _context;

définissez - le dans le constructeur qui crée votre UI:

var _context = SynchronizationContext.Current;

quand vous voulez mettre à jour l'étiquette:

_context.Send(status =>{
    // UPDATE LABEL
}, null);
11
répondu blackmind 2017-03-26 10:10:52

la façon la plus facile de penser:

   void Update()
   {
       BeginInvoke((Action)delegate()
       {
           //do your update
       });
   }
6
répondu Vasily Semenov 2014-02-18 04:41:41

par exemple, accéder à une commande autre que dans le thread courant:

Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
    lblThreshold.Text = Speed_Threshold.ToString();
}));

là, le lblThreshold est une étiquette et Speed_Threshold est une variable globale.

6
répondu Da Xiong 2017-03-26 10:11:42

lorsque vous êtes dans le thread de L'interface utilisateur, vous pouvez lui demander son planificateur de tâche de contexte de synchronisation. Il vous donnerait un TaskScheduler qui planifie tout sur le fil UI.

alors vous pouvez enchaîner vos tâches de sorte que lorsque le résultat est prêt puis une autre tâche (qui est prévue sur le thread UI) le sélectionne et l'assigne à une étiquette.

public partial class MyForm : Form
{
  private readonly TaskScheduler _uiTaskScheduler;
  public MyForm()
  {
    InitializeComponent();
    _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  }

  private void buttonRunAsyncOperation_Click(object sender, EventArgs e)
  {
    RunAsyncOperation();
  }

  private void RunAsyncOperation()
  {
    var task = new Task<string>(LengthyComputation);
    task.ContinueWith(antecedent =>
                         UpdateResultLabel(antecedent.Result), _uiTaskScheduler);
    task.Start();
  }

  private string LengthyComputation()
  {
    Thread.Sleep(3000);
    return "47";
  }

  private void UpdateResultLabel(string text)
  {
    labelResult.Text = text;
  }
}

cela fonctionne pour les tâches (pas les fils) qui sont le façon préférée d'écrire le code concurrent maintenant .

6
répondu nosalan 2017-03-26 10:15:32

je viens de lire les réponses et que cela semble être un sujet très chaud. J'utilise actuellement les formulaires .NET 3.5 SP1 et Windows.

la formule bien connue fortement décrite dans les réponses précédentes qui fait usage de la propriété InvokeRequired couvre la plupart des cas, mais pas l'ensemble.

Que faire si le Handle n'a pas encore été créé?

le InvokeRequired propriété, comme décrit ici (contrôle.InvokeRequired référence de propriété à MSDN) "15190920" retourne true si l'appel a été fait à partir d'un thread qui n'est pas le thread GUI, false soit si l'appel a été fait à partir du thread GUI, soit si le Handle n'a pas encore été créé.

vous pouvez rencontrer une exception si vous voulez avoir un formulaire modal affiché et mis à jour par un autre thread. Parce que vous voulez que ce formulaire soit affiché modal, vous pouvez faire ce qui suit:

private MyForm _gui;

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

et le délégué peut mettre à jour une étiquette sur le 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!";
}

cela peut causer une InvalidOperationException si les opérations avant la mise à jour de l'étiquette" prennent moins de temps "(lisez-le et interprétez-le comme une simplification) que le temps qu'il faut au fil GUI pour créer la forme 's poignée . Ce qui se passe à l'intérieur de la Méthode ShowDialog () .

vous devriez également vérifier pour le poignée comme ceci:

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!";
}

vous pouvez gérer l'opération à effectuer si le Gérer n'a pas encore été créé: vous pouvez simplement ignorer la mise à jour de L'interface graphique (comme indiqué dans le code ci-dessus) ou vous pouvez attendre (plus risqué). Cela devrait répondre à la question.

en option: Personnellement je suis venu codant le

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

j'alimente mes formulaires qui sont mis à jour par un autre thread avec une instance de ce ThreadSafeGuiCommand , et je définis des méthodes qui mettent à jour L'interface graphique (dans ma forme) comme ceci:

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

de cette façon, je suis tout à fait sûr que je vais avoir mon interface graphique mise à jour quel que soit le thread qui fera l'appel, attendant optionnellement une durée bien définie (le temps mort).

6
répondu Sume 2017-03-26 10:20:01

je voulais ajouter un avertissement parce que j'ai remarqué que certaines des solutions simples omettent le contrôle InvokeRequired .

j'ai remarqué que si votre code exécute avant que la poignée de la fenêtre de la commande ait été créée (par exemple avant que la forme soit affichée), Invoke lance une exception. Je recommande donc de toujours vérifier InvokeRequired avant d'appeler Invoke ou BeginInvoke .

5
répondu Jos Bosmans 2014-02-06 09:46:30