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?
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!
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.
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 è:
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";
}
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:
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.
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...");
}
}
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
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");
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.
La soluzione semplice è usare Control.Invoke
.
void DoSomething()
{
if (InvokeRequired) {
Invoke(new MethodInvoker(updateGUI));
} else {
// Do Something
updateGUI();
}
}
void updateGUI() {
// update gui here
}
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.
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.
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.
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;
}
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
}
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.
BeginInvoke
troppo frequentemente o che potrebbe sovraccaricare il pump dei messaggi.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.
Control.Invoke
o Control.BeginInvoke
che li accoppia strettamente.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");
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
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!")
Altrimenti, l'originale è una soluzione molto bella.
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).
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.
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()
.
Basta usare qualcosa come questo:
this.Invoke((MethodInvoker)delegate
{
progressBar1.Value = e.ProgressPercentage; // runs on UI thread
});
Puoi utilizzare il delegato già esistente Action
:
private void UpdateMethod()
{
if (InvokeRequired)
{
Invoke(new Action(UpdateMethod));
}
}
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".
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);
}
}
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);
È necessario utilizzare invoke e delegare
private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
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";
});
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.
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).
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 .
Il modo più semplice che penso:
void Update()
{
BeginInvoke((Action)delegate()
{
//do your update
});
}
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
}));
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;
});
}
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 */ }));
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
.
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;
}
}
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); }
}
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
});
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
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;
}
);
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();
}));
Preferisco questo:
private void UpdateNowProcessing(string nowProcessing)
{
if (this.InvokeRequired)
{
Action<string> d = UpdateNowProcessing;
Invoke(d, nowProcessing);
}
else
{
this.progressDialog.Next(nowProcessing);
}
}
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";
}
}
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();
}
}
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
}
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.
Il modo più semplice è invocare come segue:
Application.Current.Dispatcher.Invoke(new Action(() =>
{
try
{
///
}
catch (Exception)
{
//
}
}
));