Sono diventato dolorosamente consapevole di quanto spesso è necessario scrivere il seguente modello di codice nel codice GUI event-driven, dove
private void DoGUISwitch() { // cruisin for a bruisin' through exception city object1.Visible = true; object2.Visible = false; }
diventa:
private void DoGUISwitch() { if (object1.InvokeRequired) { object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); })); } else { object1.Visible = true; object2.Visible = false; } }
Questo è uno schema scomodo in C #, sia per ricordare che per scrivere. Qualcuno ha escogitato una sorta di scorciatoia o costrutto che lo automatizza fino a un certo punto? Sarebbe bello se ci fosse un modo per colbind una funzione agli oggetti che esegue questo controllo senza dover passare attraverso tutto questo lavoro extra, come un object1.InvokeIfNecessary.visible = true
type shortcut.
Le risposte precedenti hanno discusso l’impraticabilità della semplice chiamata di Invoke () ogni volta, e anche in questo caso la syntax di Invoke () è sia inefficiente che ancora scomoda da gestire.
Quindi, qualcuno ha capito qualche scorciatoia?
L’approccio di Lee può essere ulteriormente semplificato
public static void InvokeIfRequired(this Control control, MethodInvoker action) { // See Update 2 for edits Mike de Klerk suggests to insert here. if (control.InvokeRequired) { control.Invoke(action); } else { action(); } }
E può essere chiamato così
richEditControl1.InvokeIfRequired(() => { // Do anything you want with the control here richEditControl1.RtfText = value; RtfHelpers.AddMissingStyles(richEditControl1); });
Non è necessario passare il controllo come parametro al delegato. C # crea automaticamente una chiusura .
AGGIORNAMENTO :
Secondo molti altri poster, Control
può essere generalizzato come ISynchronizeInvoke
:
public static void InvokeIfRequired(this ISynchronizeInvoke obj, MethodInvoker action) { if (obj.InvokeRequired) { var args = new object[0]; obj.Invoke(action, args); } else { action(); } }
DonBoitnott ha sottolineato che, a differenza di Control
l’interfaccia ISynchronizeInvoke
richiede un array di oggetti per il metodo Invoke
come elenco di parametri per l’ action
.
AGGIORNAMENTO 2
Modifiche suggerite da Mike de Klerk (vedere il commento nel primo frammento di codice per il punto di inserimento):
// When the form, thus the control, isn't visible yet, InvokeRequired returns false, // resulting still in a cross-thread exception. while (!control.Visible) { System.Threading.Thread.Sleep(50); }
Vedi il commento di ToolmakerSteve di seguito per dubbi su questo suggerimento.
Potresti scrivere un metodo di estensione:
public static void InvokeIfRequired(this Control c, Action action) { if(c.InvokeRequired) { c.Invoke(new Action(() => action(c))); } else { action(c); } }
E usalo in questo modo:
object1.InvokeIfRequired(c => { c.Visible = true; });
EDIT: come Simpzon sottolinea nei commenti si potrebbe anche cambiare la firma in:
public static void InvokeIfRequired(this T c, Action action) where T : Control
Ecco il modulo che ho usato in tutto il mio codice.
private void DoGUISwitch() { Invoke( ( MethodInvoker ) delegate { object1.Visible = true; object2.Visible = false; }); }
Ho basato questo sul post del blog qui . Non ho avuto questo approccio mi fallire, quindi non vedo alcun motivo per complicare il mio codice con un controllo della proprietà InvokeRequired
.
Spero che questo ti aiuti.
Crea un file ThreadSafeInvoke.snippet, quindi puoi semplicemente selezionare le istruzioni di aggiornamento, fare clic con il tasto destro e selezionare ‘Surround With …’ o Ctrl-K + S:
ThreadsafeInvoke Wraps code in an anonymous method passed to Invoke for Thread safety. SurroundsWith
Ecco una versione migliorata / combinata delle risposte di Lee, Oliver e Stephan.
public delegate void InvokeIfRequiredDelegate(T obj) where T : ISynchronizeInvoke; public static void InvokeIfRequired (this T obj, InvokeIfRequiredDelegate action) where T : ISynchronizeInvoke { if (obj.InvokeRequired) { obj.Invoke(action, new object[] { obj }); } else { action(obj); } }
Il modello consente un codice flessibile e senza cast, che è molto più leggibile mentre il delegato dedicato fornisce efficienza.
progressBar1.InvokeIfRequired(o => { o.Style = ProgressBarStyle.Marquee; o.MarqueeAnimationSpeed = 40; });
Preferisco utilizzare una singola istanza di un metodo Delega invece di creare una nuova istanza ogni volta. Nel mio caso ero solito mostrare messaggi di progresso e (info / errore) da un Backroundworker che copiava e trasmetteva grandi dati da un’istanza sql. Ogni volta, dopo circa 70000 progressi e chiamate, il mio modulo ha smesso di funzionare e mostra nuovi messaggi. Ciò non si è verificato quando ho iniziato a utilizzare un unico delegato di istanza globale.
delegate void ShowMessageCallback(string message); private void Form1_Load(object sender, EventArgs e) { ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage); } private void ShowMessage(string message) { if (this.InvokeRequired) this.Invoke(showMessageDelegate, message); else labelMessage.Text = message; } void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e) { ShowMessage(e.Message); }
Uso:
control.InvokeIfRequired(c => c.Visible = false); return control.InvokeIfRequired(c => { c.Visible = value return c.Visible; });
Codice:
public static class SynchronizeInvokeExtensions { public static void InvokeIfRequired(this T obj, Action action) where T : ISynchronizeInvoke { if (obj.InvokeRequired) obj.Invoke(action, new object[] { obj }); else action(obj); } public static TOut InvokeIfRequired(this TIn obj, Func func) where TIn : ISynchronizeInvoke => obj.InvokeRequired ? (TOut)obj.Invoke(func, new object[] { obj }) : func(obj); }
Mi piace farlo un po ‘diverso, mi piace chiamare “me stesso” se necessario con un’azione,
private void AddRowToListView(ScannerRow row, bool suspend) { if (IsFormClosing) return; if (this.InvokeRequired) { var A = new Action(() => AddRowToListView(row, suspend)); this.Invoke(A); return; } //as of here the Code is thread-safe
questo è un modello a portata di mano, IsFormClosing è un campo che ho impostato su True quando sto chiudendo il mio modulo in quanto potrebbero esserci alcuni thread in background che sono ancora in esecuzione …
Non dovresti mai scrivere codice simile a questo:
private void DoGUISwitch() { if (object1.InvokeRequired) { object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); })); } else { object1.Visible = true; object2.Visible = false; } }
Se hai un codice simile a questo, la tua applicazione non è thread-safe. Significa che hai un codice che sta già chiamando DoGUISwitch () da un thread diverso. È troppo tardi per controllare se è in una discussione diversa. InvokeRequire deve essere chiamato PRIMA di effettuare una chiamata a DoGUISwitch. Non si dovrebbe accedere a nessun metodo o proprietà da un thread diverso.
Riferimento: Control.InvokeRequired Proprietà in cui è ansible leggere quanto segue:
Oltre alla proprietà InvokeRequired, esistono quattro metodi su un controllo che possono essere richiamati da thread: Invoke, BeginInvoke, EndInvoke e CreateGraphics se l’handle per il controllo è già stato creato.
In un’architettura a singola CPU non c’è alcun problema, ma in un’architettura multi-CPU è ansible assegnare parte del thread dell’interfaccia utente al processore su cui era in esecuzione il codice chiamante … e se quel processore è diverso da quello in cui il thread dell’interfaccia utente era in esecuzione quando il thread chiamante finisce Windows penserà che il thread dell’interfaccia utente è terminato e ucciderà il processo dell’applicazione, ovvero l’applicazione uscirà senza errori.