it-swarm.it

Come aggiorno la GUI da un altro thread?

Qual è il modo più semplice per aggiornare un Label da un altro thread?

Ho un Form su thread1, e da quello sto avviando un altro thread (thread2). Mentre thread2 elabora alcuni file, vorrei aggiornare un Label sul Form con lo stato corrente del lavoro di thread2.

Come lo posso fare?

1268
CruelIO

Per .NET 2.0, ecco un bel po 'di codice che ho scritto che fa esattamente quello che vuoi, e funziona per qualsiasi proprietà su 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 });
  }
}

Chiamalo così:

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

Se si utilizza .NET 3.0 o versione successiva, è possibile riscrivere il metodo precedente come metodo di estensione della classe Control, che potrebbe quindi semplificare la chiamata a:

myLabel.SetPropertyThreadSafe("Text", status);

AGGIORNAMENTO 05/10/2010:

Per .NET 3.0 dovresti usare questo codice:

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 ||
      [email protected]().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 });
  }
}

che usa le espressioni LINQ e lambda per consentire una sintassi molto più pulita, più semplice e più sicura:

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

Non solo il nome della proprietà ora è verificato in fase di compilazione, anche il tipo della proprietà è, quindi è impossibile (ad esempio) assegnare un valore stringa a una proprietà booleana e quindi causare un'eccezione di runtime.

Sfortunatamente questo non impedisce a nessuno di fare cose stupide come passare in un'altra proprietà e valore di Control, quindi la seguente compilazione sarà felicemente:

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

Quindi ho aggiunto i controlli di runtime per assicurarmi che la proprietà passata appartenga effettivamente al Control in cui viene chiamato il metodo. Non perfetto, ma ancora molto meglio della versione .NET 2.0.

Se qualcuno ha ulteriori suggerimenti su come migliorare questo codice per la sicurezza in fase di compilazione, si prega di commentare!

735
Ian Kemp

Il simplest way è un metodo anonimo passato in 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

Si noti che Invoke blocca l'esecuzione fino al suo completamento - questo è il codice sincrono. La domanda non riguarda il codice asincrono, ma c'è molto contenuto su Stack Overflow sulla scrittura di codice asincrono quando si vuole imparare a riguardo.

1001
Marc Gravell

Gestire un lavoro lungo

Poiché .NET 4.5 e C # 5.0 si dovrebbe usare Asynchronous Pattern (TAP) basato su attività insieme a async - await keywords in tutte le aree (inclusa la GUI):

TAP è il modello di progettazione asincrono consigliato per il nuovo sviluppo

invece di Asymchronous Programming Model (APM) e Pattern asincrono basato sugli eventi (EAP) (quest'ultimo include/ Classe BackgroundWorker ).

Quindi, la soluzione consigliata per il nuovo sviluppo è:

  1. Implementazione asincrona di un gestore di eventi (Sì, questo è tutto):

    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. Implementazione del secondo thread che notifica il thread dell'interfaccia utente:

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

Si noti quanto segue:

  1. Codice breve e pulito scritto in modo sequenziale senza callback e thread espliciti.
  2. Task invece di Thread .
  3. async keyword, che consente di utilizzare await che a sua volta impedisce al gestore di eventi di raggiungere lo stato di completamento fino al completamento dell'attività e nel frattempo non blocca il thread dell'interfaccia utente.
  4. Progress class (vedi Interfaccia IProgress ) che supporta Separation of Concerns (SoC) principio di progettazione e non richiede esplicitamente dispatcher e invocazioni. Utilizza il corrente SynchronizationContext dalla sua posizione di creazione (qui il thread dell'interfaccia utente).
  5. TaskCreationOptions.LongRunning che suggerisce di non accodare l'attività in ThreadPool .

Per un esempio più dettagliato vedi: The Future of C #: Le cose buone arrivano a coloro che "attendono" by Joseph Albahari .

Vedi anche su UI Threading Model concept.

Gestire le eccezioni

Il frammento di seguito è un esempio di come gestire le eccezioni e attivare la proprietà Enabled del pulsante per impedire più clic durante l'esecuzione in background.

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...");
    }
}
371
Ryszard Dżegan

Variazione di Marc Gravell's simplest solution per .NET 4:

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

Oppure usa invece il delegato di Action:

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

Vedi qui per un confronto tra i due: MethodInvoker vs Action per Control.BeginInvoke

218
Zaid Masud

Ignora e dimentica il metodo di estensione per .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();
        }
    }
}

Questo può essere chiamato usando la seguente riga di codice:

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

Questo è il modo classico per farlo:

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

Il tuo thread di lavoro ha un evento. Il thread dell'interfaccia utente avvia un altro thread per eseguire il lavoro e collega quell'evento worker in modo da poter visualizzare lo stato del thread worker.

Quindi nell'interfaccia utente è necessario incrociare i thread per modificare il controllo effettivo ... come un'etichetta o una barra di avanzamento.

64
Hath

La soluzione semplice è usare Control.Invoke.

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

void updateGUI() {
    // update gui here
}
59
OregonGhost

Il codice di threading è spesso buggato e sempre difficile da testare. Non è necessario scrivere codice di threading per aggiornare l'interfaccia utente da un'attività in background. Basta usare BackgroundWorker class per eseguire l'attività e il suo ReportProgress metodo per aggiornare l'interfaccia utente. Di solito, si segnala solo una percentuale completa, ma c'è un altro sovraccarico che include un oggetto stato. Ecco un esempio che riporta solo un oggetto stringa:

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

Va bene se vuoi sempre aggiornare lo stesso campo. Se sono necessari aggiornamenti più complicati, è possibile definire una classe per rappresentare lo stato dell'interfaccia utente e passarlo al metodo ReportProgress.

Un'ultima cosa, assicurati di impostare il flag WorkerReportsProgress, altrimenti il ​​metodo ReportProgress verrà completamente ignorato.

45
Don Kirkby

La stragrande maggioranza delle risposte usa Control.Invoke che è a condizione della razza in attesa di succedere . Ad esempio, considera la risposta accettata:

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

Se l'utente chiude il modulo poco prima che venga chiamato this.Invoke (ricorda, this è l'oggetto Form), un ObjectDisposedException sarà probabilmente attivato.

La soluzione è usare SynchronizationContext, in particolare SynchronizationContext.Current as hamilton.danielb suggerisce (altre risposte si basano su specifiche implementazioni SynchronizationContext che è completamente inutile). Modificherei leggermente il suo codice per usare SynchronizationContext.Post piuttosto che SynchronizationContext.Send anche se (in genere non c'è bisogno che il thread worker attenda):

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

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

Nota che su .NET 4.0 e versioni successive dovresti davvero utilizzare le attività per le operazioni asincrone. Vedi n-san's answer per l'approccio basato sull'attività equivalente (usando TaskScheduler.FromCurrentSynchronizationContext).

Infine, su .NET 4.5 e versioni successive è possibile utilizzare Progress<T> (che in pratica acquisisce SynchronizationContext.Current al momento della sua creazione) come dimostrato da Ryszard Dżegan's per i casi in cui l'operazione di lunga durata deve eseguire il codice UI mentre funziona.

39
Ohad Schneider

Dovrai assicurarti che l'aggiornamento avvenga sul thread corretto; il thread dell'interfaccia utente.

Per fare ciò, dovrai richiamare il gestore di eventi invece di chiamarlo direttamente.

Puoi farlo alzando il tuo evento in questo modo:

(Il codice è stato digitato qui fuori dalla mia testa, quindi non ho controllato la sintassi corretta, ecc., Ma dovrebbe farti andare.)

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

Si noti che il codice sopra non funzionerà su progetti WPF, poiché i controlli WPF non implementano l'interfaccia ISynchronizeInvoke.

Per assicurarti che il codice sopra funzioni con Windows Form e WPF e tutte le altre piattaforme, puoi dare un'occhiata alle classi AsyncOperation, AsyncOperationManager e SynchronizationContext.

Al fine di aumentare facilmente gli eventi in questo modo, ho creato un metodo di estensione, che mi consente di semplificare l'innalzamento di un evento semplicemente chiamando:

MyEvent.Raise(this, EventArgs.Empty);

Naturalmente, puoi anche utilizzare la classe BackGroundWorker, che estrae l'argomento per te.

34

Avrai bisogno di richiamare il metodo sul thread della GUI. È possibile farlo chiamando Control.Invoke.

Per esempio:

delegate void UpdateLabelDelegate (string message);

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

    MyLabelControl.Text = message;
}
28
Kieron

Non è necessaria nessuna delle voci Invoke nelle risposte precedenti.

È necessario guardare 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
}
26
Jon H

A causa della banalità dello scenario, avrei effettivamente il polling del thread dell'interfaccia utente per lo stato. Penso che troverai che può essere abbastanza elegante.

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'approccio evita l'operazione di marshalling richiesta quando si utilizzano i metodi ISynchronizeInvoke.Invoke e ISynchronizeInvoke.BeginInvoke. Non c'è niente di sbagliato nell'usare la tecnica di marshalling, ma ci sono un paio di avvertenze da tenere presente.

  • Assicurati di non chiamare BeginInvoke troppo frequentemente o che potrebbe sovraccaricare il pump dei messaggi.
  • Chiamare Invoke sul thread worker è una chiamata bloccante. Interromperà temporaneamente il lavoro svolto in quel thread.

La strategia che propongo in questa risposta ribalta i ruoli comunicativi dei thread. Invece del thread worker che inserisce i dati, il thread UI esegue il polling per esso. Questo è un modello comune utilizzato in molti scenari. Dal momento che tutto ciò che si vuole fare è visualizzare le informazioni sull'avanzamento dal thread di lavoro, penso che troverete che questa soluzione è un'ottima alternativa alla soluzione di marshalling. Presenta i seguenti vantaggi.

  • L'interfaccia utente e i thread di lavoro rimangono accoppiati liberamente anziché l'approccio Control.Invoke o Control.BeginInvoke che li accoppia strettamente.
  • Il thread dell'interfaccia utente non impedirà l'avanzamento del thread di lavoro.
  • Il thread di lavoro non può dominare il momento in cui il thread dell'interfaccia utente spende l'aggiornamento.
  • Gli intervalli in cui l'interfaccia utente e i thread di lavoro eseguono operazioni possono rimanere indipendenti.
  • Il thread di lavoro non può superare la pompa dei messaggi del thread dell'interfaccia utente.
  • Il thread dell'interfaccia utente consente di stabilire quando e quanto spesso l'interfaccia utente viene aggiornata.
26
Brian Gideon

Questo è simile alla soluzione sopra usando .NET Framework 3.0, ma ha risolto il problema di supporto di sicurezza in fase di compilazione .

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

Usare:

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

Il compilatore fallirà se l'utente passa il tipo di dati errato.

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

Salvete! Dopo aver cercato questa domanda, ho trovato le risposte di FrankG e Oregon Ghost per essere il più facile per me. Ora, codice in Visual Basic e ho eseguito questo snippet tramite un convertitore; quindi non sono sicuro di come andrà a finire.

Ho una finestra di dialogo chiamata form_Diagnostics, che ha una casella di testo in chiaro, chiamata updateDiagWindow, che sto usando come una sorta di display di registrazione. Dovevo essere in grado di aggiornare il suo testo da tutti i thread. Le linee aggiuntive consentono alla finestra di scorrere automaticamente verso le righe più recenti.

E così, ora posso aggiornare il display con una linea, da qualsiasi punto dell'intero programma, nel modo in cui pensi che funzionerebbe senza alcun threading:

  form_Diagnostics.updateDiagWindow(whatmessage);

Codice principale (inserisci questo codice all'interno del codice del modulo):

#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
22
bgmCoder

Questo nella mia variante C # 3.0 della soluzione di 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);
}

Lo chiami così:

myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
  1. Aggiunge il controllo nullo al risultato del "come MemberExpression".
  2. Migliora la sicurezza del tipo statico.

Altrimenti, l'originale è una soluzione molto bella.

20
Rotaerk

Per molti scopi è così semplice:

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

"serviceGUI ()" è un metodo a livello di interfaccia grafica all'interno del modulo (questo) che può modificare tutti i controlli desiderati. Chiama "updateGUI ()" dall'altro thread. I parametri possono essere aggiunti ai valori di passaggio o (probabilmente più velocemente) utilizzare variabili di ambito di classe con blocchi su di essi come richiesto se esiste una possibilità di uno scontro tra i thread che li accedono e che potrebbero causare instabilità. Utilizzare BeginInvoke anziché Invoke se il thread non-GUI è critico in termini di tempo (tenendo presente l'avvertimento di Brian Gideon in mente).

20
Frankg

Quando ho incontrato lo stesso problema ho cercato aiuto da Google, ma invece di darmi una soluzione semplice mi ha confuso di più dando esempi di MethodInvoker e blah blah blah. Così ho deciso di risolverlo da solo. Ecco la mia soluzione:

Crea un delegato in questo modo:

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
}

Puoi chiamare questa funzione in una nuova discussione come questa

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

Non essere confuso con Thread(() => .....). Uso una funzione anonima o un'espressione lambda quando lavoro su un thread. Per ridurre le linee di codice puoi usare anche il metodo ThreadStart(..) che non dovrei spiegare qui.

19
ahmar
Label lblText; //initialized elsewhere

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

   lblText.Text = text;           
}

Si noti che BeginInvoke() è preferito su Invoke() perché è meno probabile che generi deadlock (tuttavia, qui non si tratta di un problema quando si assegna semplicemente un testo a un'etichetta):

Quando si utilizza Invoke() si sta aspettando il ritorno del metodo. Ora, potrebbe accadere che tu faccia qualcosa nel codice invocato che dovrà attendere il thread, il che potrebbe non essere immediatamente ovvio se è sepolto in alcune funzioni che stai chiamando, cosa che potrebbe accadere indirettamente tramite i gestori di eventi. Quindi aspetteresti il ​​thread, il thread ti aspetterebbe e sarai bloccato.

Ciò ha causato l'hang di alcuni dei nostri software rilasciati. È stato abbastanza facile risolvere sostituendo Invoke() con BeginInvoke(). A meno che non si abbia bisogno di un'operazione sincrona, che può essere il caso se si ha bisogno di un valore di ritorno, utilizzare BeginInvoke().

19
ILoveFortran

Basta usare qualcosa come questo:

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

Puoi utilizzare il delegato già esistente Action:

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

La mia versione è inserire una riga di "mantra" ricorsivo:

Per nessun argomento:

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

        // Your code!
    }

Per una funzione che ha argomenti:

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

QUELLO è IT .


Alcune argomentazioni : Solitamente non è corretto per la leggibilità del codice inserire {} dopo un'istruzione if () in una riga. Ma in questo caso è lo stesso "mantra" di routine. Non infrange la leggibilità del codice se questo metodo è coerente sul progetto. E salva il tuo codice dai rifiuti (una riga di codice anziché cinque).

Come vedi if(InvokeRequired) {something long} sai semplicemente "questa funzione è sicura per chiamare da un altro thread".

14
MajesticRa

Prova ad aggiornare l'etichetta usando questo

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

    public static void Refresh(this UIElement uiElement)
    {
        uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }
}
13
user1360355

Crea una variabile di classe:

SynchronizationContext _context;

Impostalo nel costruttore che crea l'interfaccia utente:

var _context = SynchronizationContext.Current;

Quando si desidera aggiornare l'etichetta:

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

È necessario utilizzare invoke e delegare

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

La maggior parte delle altre risposte sono un po 'complesse per me su questa domanda (sono nuovo di C #), quindi scrivo il mio:

Ho unWPFapplication e ho definito un worker come di seguito:

Problema:

BackgroundWorker workerAllocator;
workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1) {
    // This is my DoWork function.
    // It is given as an anonymous function, instead of a separate DoWork function

    // I need to update a message to textbox (txtLog) from this thread function

    // Want to write below line, to update UI
    txt.Text = "my message"

    // But it fails with:
    //  'System.InvalidOperationException':
    //  "The calling thread cannot access this object because a different thread owns it"
}

Soluzione:

workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1)
{
    // The below single line works
    txtLog.Dispatcher.BeginInvoke((Action)(() => txtLog.Text = "my message"));
}

Devo ancora scoprire cosa significa la linea sopra, ma funziona.

Per WinForms :

Soluzione:

txtLog.Invoke((MethodInvoker)delegate
{
    txtLog.Text = "my message";
});
9

Ad esempio, accedere a un controllo diverso dal thread corrente:

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

Qui lblThreshold è un'etichetta e Speed_Threshold è una variabile globale.

8
Da Xiong

Ho appena letto le risposte e questo sembra essere un argomento molto caldo. Attualmente sto usando .NET 3.5 SP1 e Windows Forms.

La formula ben nota ampiamente descritta nelle risposte precedenti che utilizza la proprietà InvokeRequired copre la maggior parte dei casi, ma non l'intero pool.

Cosa succede se il Handle non è stato ancora creato?

La proprietà InvokeRequired , come descritto qui (Riferimento proprietà Control.InvokeRequired a MSDN) restituisce true se la chiamata è stata effettuata da un thread che non è il thread della GUI, falso se la chiamata è stata effettuata dal thread GUI, o se Handle non è stato ancora creato.

È possibile riscontrare un'eccezione se si desidera visualizzare e aggiornare un modulo modale da un altro thread. Poiché desideri che il modulo venga mostrato in modo modale, puoi eseguire le seguenti operazioni:

private MyForm _gui;

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

E il delegato può aggiornare un'etichetta sulla 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!";
}

Ciò può causare un InvalidOperationException se le operazioni prima dell'aggiornamento dell'etichetta "richiedono meno tempo" (leggerlo e interpretarlo come una semplificazione) rispetto al tempo impiegato dal thread della GUI per creare il Form 's Handle . Questo accade all'interno del ShowDialog () metodo.

Dovresti anche controllare per Handle come questo:

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

Puoi gestire l'operazione da eseguire se Handle non è stato ancora creato: puoi semplicemente ignorare l'aggiornamento della GUI (come mostrato nel codice sopra) o aspettare (più rischioso). Questo dovrebbe rispondere alla domanda.

Cose opzionali: Personalmente mi sono inventato il codice seguente:

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

Inserisco i miei moduli che vengono aggiornati da un altro thread con un'istanza di this ThreadSafeGuiCommand , e definisco i metodi che aggiornano la GUI (nel mio Form) in questo modo:

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

In questo modo sono abbastanza sicuro che avrò aggiornato la mia GUI qualunque thread effettui la chiamata, eventualmente in attesa di un intervallo di tempo ben definito (il timeout).

8
Sume

Quando si è nel thread dell'interfaccia utente, è possibile chiedergli il relativo programma di pianificazione del contesto di sincronizzazione. Ti darebbe un TaskScheduler che pianifica tutto sul thread dell'interfaccia utente.

Quindi puoi concatenare le tue attività in modo che quando il risultato è pronto, un'altra attività (che è pianificata sul thread dell'interfaccia utente) la preleva e la assegna a un'etichetta.

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

Questo funziona per task (non thread) che sono il modo preferito di scrivere codice simultaneo ora .

8
nosalan

Il modo più semplice che penso:

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

Il modo più semplice per le applicazioni WPF è:

this.Dispatcher.Invoke((Action)(() =>
{
    // This refers to a form in a WPF application 
    val1 = textBox.Text; // Access the UI 
}));
7

Anche se l'operazione richiede molto tempo (thread.sleep nel mio esempio) - Questo codice NON bloccherà l'interfaccia utente:

 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; 
     });
 }
6
Yuliia Ashomok

Non ho potuto ottenere la logica di Microsoft dietro questa brutta implementazione, ma devi avere due funzioni:

void setEnableLoginButton()
{
  if (InvokeRequired)
  {
    // btn_login can be any conroller, (label, button textbox ..etc.)

    btn_login.Invoke(new MethodInvoker(setEnable));

    // OR
    //Invoke(new MethodInvoker(setEnable));
  }
  else {
    setEnable();
  }
}

void setEnable()
{
  btn_login.Enabled = isLoginBtnEnabled;
}

Questi frammenti funzionano per me, quindi posso fare qualcosa su un altro thread e quindi aggiorno la GUI:

Task.Factory.StartNew(()=>
{
    // THIS IS NOT GUI
    Thread.Sleep(5000);
    // HERE IS INVOKING GUI
    btn_login.Invoke(new Action(() => DoSomethingOnGUI()));
});

private void DoSomethingOnGUI()
{
   // GUI
   MessageBox.Show("message", "title", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}

Ancora più semplice:

btn_login.Invoke(new Action(()=>{ /* HERE YOU ARE ON GUI */ }));
6
MBH

Volevo aggiungere un avviso perché ho notato che alcune delle semplici soluzioni omettevano il controllo InvokeRequired.

Ho notato che se il codice esegue prima che l'handle della finestra del controllo sia stato creato (ad es. Prima che venga mostrato il modulo), Invoke genera un'eccezione. Quindi consiglio sempre di verificare InvokeRequired prima di chiamare Invoke o BeginInvoke.

6
Jos Bosmans

Ecco un nuovo aspetto su un problema secolare che utilizza uno stile più funzionale. Se mantieni la classe TaskXM in tutti i tuoi progetti hai solo una riga di codice per non preoccuparti più degli aggiornamenti cross-thread.

public class Example
{
    /// <summary>
    /// No more delegates, background workers, etc. Just one line of code as shown below.
    /// Note it is dependent on the Task Extension method shown next.
    /// </summary>
    public async void Method1()
    {
        // Still on the GUI thread here if the method was called from the GUI thread
        // This code below calls the extension method which spins up a new task and calls back.
        await TaskXM.RunCodeAsync(() =>
        {
            // Running an asynchronous task here
            // Cannot update the GUI thread here, but can do lots of work
        });
        // Can update GUI on this line
    }
}


/// <summary>
/// A class containing extension methods for the Task class
/// </summary>
public static class TaskXM
{
    /// <summary>
    /// RunCodeAsyc is an extension method that encapsulates the Task.run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunCodeAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}
5
John Peters

Forse un po 'overdose, ma questo è il tipo di modo in cui risolvo questo normalmente:

I richiami non sono richiesti qui a causa della sincronizzazione. BasicClassThreadExample è solo un tipo di layout per me, quindi modificalo per adattarlo alle tue reali esigenze.

È semplice perché non hai bisogno di gestire le cose nel thread dell'interfaccia utente!

public partial class Form1 : Form
{
    BasicClassThreadExample _example;

    public Form1()
    {
        InitializeComponent();
        _example = new BasicClassThreadExample();
        _example.MessageReceivedEvent += _example_MessageReceivedEvent;
    }

    void _example_MessageReceivedEvent(string command)
    {
        listBox1.Items.Add(command);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        listBox1.Items.Clear();
        _example.Start();
    }
}

public class BasicClassThreadExample : IDisposable
{
    public delegate void MessageReceivedHandler(string msg);

    public event MessageReceivedHandler MessageReceivedEvent;

    protected virtual void OnMessageReceivedEvent(string msg)
    {
        MessageReceivedHandler handler = MessageReceivedEvent;
        if (handler != null)
        {
            handler(msg);
        }
    }

    private System.Threading.SynchronizationContext _SynchronizationContext;
    private System.Threading.Thread _doWorkThread;
    private bool disposed = false;

    public BasicClassThreadExample()
    {
        _SynchronizationContext = System.ComponentModel.AsyncOperationManager.SynchronizationContext;
    }

    public void Start()
    {
        _doWorkThread = _doWorkThread ?? new System.Threading.Thread(dowork);

        if (!(_doWorkThread.IsAlive))
        {
            _doWorkThread = new System.Threading.Thread(dowork);
            _doWorkThread.IsBackground = true;
            _doWorkThread.Start();
        }
    }

    public void dowork()
    {
        string[] retval = System.IO.Directory.GetFiles(@"C:\Windows\System32", "*.*", System.IO.SearchOption.TopDirectoryOnly);
        foreach (var item in retval)
        {
            System.Threading.Thread.Sleep(25);
            _SynchronizationContext.Post(new System.Threading.SendOrPostCallback(delegate(object obj)
            {
                OnMessageReceivedEvent(item);
            }), null);
        }
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                _doWorkThread.Abort();
            }
            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~BasicClassThreadExample() { Dispose(false); }

}
5
Carsten R.

Un altro esempio sull'argomento: ho creato una classe astratta, UiSynchronizeModel, che contiene un'implementazione di metodo comune:

public abstract class UiSynchronizeModel
{
    private readonly TaskScheduler uiSyncContext;
    private readonly SynchronizationContext winformsOrDefaultContext;

    protected UiSynchronizeModel()
    {
        this.winformsOrDefaultContext = SynchronizationContext.Current ?? new SynchronizationContext();
        this.uiSyncContext = TaskScheduler.FromCurrentSynchronizationContext();
    }

    protected void RunOnGuiThread(Action action)
    {
        this.winformsOrDefaultContext.Post(o => action(), null);
    }

    protected void CompleteTask(Task task, TaskContinuationOptions options, Action<Task> action)
    {
        task.ContinueWith(delegate
        {
            action(task);
            task.Dispose();
        }, CancellationToken.None, options, this.uiSyncContext);
    }
}

La classe del tuo modello o controllore dovrebbe essere derivata da questa classe astratta. È possibile utilizzare qualsiasi modello (attività o thread di background gestiti manualmente) e utilizzare questi metodi in questo modo:

public void MethodThatCalledFromBackroundThread()
{
   this.RunOnGuiThread(() => {
       // Do something over UI controls
   });
}

Esempio di attività:

var task = Task.Factory.StartNew(delegate
{
    // Background code
    this.RunOnGuiThread(() => {
        // Do something over UI controls
    });
});

this.CompleteTask(task, TaskContinuationOptions.OnlyOnRanToCompletion, delegate
{
    // Code that can safely use UI controls
});
5

Fondamentalmente, il modo per risolvere questo problema indipendentemente dalla versione del framework o dal tipo di libreria sottostante della GUI è di salvare il controllo creando il contesto di sincronizzazione del thread per il thread di lavoro che eseguirà il marshalling dell'interazione relativa del controllo dal thread worker alla coda dei messaggi thread della GUI.

Esempio:

SynchronizationContext ctx = SynchronizationContext.Current; // From control
ctx.Send\Post... // From worker thread
4
Roman Ambinder

E ancora un altro generico Control extension aproach ..

Innanzitutto aggiungi un metodo di estensione per oggetti di tipo Control

public static void InvokeIfRequired<T>(this T c, Action<T> action) where T : Control
{
    if (c.InvokeRequired)
    {
        c.Invoke(new Action(() => action(c)));
    }
    else
    {
        action(c);
    }
}

e chiamare in questo modo da un altro thread per accedere a un controllo denominato object1 in UI-thread:

object1.InvokeIfRequired(c => { c.Visible = true; });
object1.InvokeIfRequired(c => { c.Text = "ABC"; });

..o così

object1.InvokeIfRequired(c => 
  { 
      c.Text = "ABC";
      c.Visible = true; 
  }
);
3
flodis

Per prima cosa prendi l'istanza del tuo modulo (in questo caso mainForm), e poi usa questo codice nell'altro thread.

mainForm.Invoke(new MethodInvoker(delegate () 
{
    // Update things in my mainForm here
    mainForm.UpdateView();
}));
3
Musculaa

Preferisco questo:

private void UpdateNowProcessing(string nowProcessing)
{
    if (this.InvokeRequired)
    {
        Action<string> d = UpdateNowProcessing;
        Invoke(d, nowProcessing);
    }
    else
    {
        this.progressDialog.Next(nowProcessing);
    }            
}
3
user523650

Metti alcune variabili comuni in una classe separata per contenere il valore.

Esempio:

public  class data_holder_for_controls
{
    // It will hold the value for your label
    public string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();

    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread
    }

    public static void perform_logic()
    {
        // Put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            // Statements here
        }
        // Set the result in the status variable
        d1.status = "Task done";
    }
}
3
Saurabh

basta usare il contesto di sincronizzazione di ui

using System.Threading;

// ...

public partial class MyForm : Form
{
    private readonly SynchronizationContext uiContext;

    public MyForm()
    {
        InitializeComponent();
        uiContext = SynchronizationContext.Current; // get ui thread context
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Thread t = new Thread(() =>
            {// set ui thread context to new thread context                            
             // for operations with ui elements to be performed in proper thread
             SynchronizationContext
                 .SetSynchronizationContext(uiContext);
             label1.Text = "some text";
            });
        t.Start();
    }
}
2
user53373

Nel mio caso (WPF) la soluzione è semplice come questa:

private void updateUI()
{
    if (!Dispatcher.CheckAccess())
    {
        Dispatcher.BeginInvoke(updateUI);
        return;
    }

    // Update any number of controls here
}
2
ipe

L'approccio generale è come:

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

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        int clickCount = 0;

        public Form1()
        {
            InitializeComponent();
            label1.SetText("0");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            new Thread(() => label1.SetText((++clickCount).ToString())).Start();
        }
    }

    public static class ControlExtensions
    {
        public static void SetText(this Control control, string text)
        {
            if (control.InvokeRequired)
                control.Invoke(setText, control, text);
            else
                control.Text = text;
        }

        private static readonly Action<Control, string> setText =
            (control, text) => control.Text = text;
    }
}

Spiegazione :

La risposta è molto simile a questa . Ma usa Neater (come per me) e la sintassi più recente. Il punto è InvokeRequired proprietà di control. Ottiene un valore che indica se il chiamante deve chiamare un metodo di richiamo quando effettua chiamate di metodo al controllo perché il chiamante si trova su un thread diverso da quello su cui è stato creato il controllo. Quindi se chiamiamo control.SetText("some text") sullo stesso thread su cui control è stato creato, va bene impostare Text come control.Text = text. Ma su qualsiasi altro thread causa System.InvalidOperationException quindi si deve chiamare un metodo tramite control.Invoke(...) per impostare Text sul thread control in cui è stato creato.

1
Alex

Il modo più semplice è invocare come segue:

 Application.Current.Dispatcher.Invoke(new Action(() =>
             {
                    try
                    {
                        ///
                    }
                    catch (Exception)
                    {
                      //
                    }


                    }
     ));
0
Lankan