Automazione del modello di codice InvokeRequired

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:

 < ?xml version="1.0" encoding="utf-8" ?>  
ThreadsafeInvoke Wraps code in an anonymous method passed to Invoke for Thread safety. SurroundsWith
< ![CDATA[ Invoke( (MethodInvoker) delegate { $selected$ }); ]]>

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.