Invoke o BeginInvoke non possono essere richiamati su un controllo finché non è stato creato l’handle della finestra

Ho un metodo di estensione di controllo SafeInvoke simile a quello discusso da Greg D qui (meno il controllo IsHandleCreated).

Lo chiamo da un System.Windows.Forms.Form come segue:

 public void Show(string text) { label.SafeInvoke(()=>label.Text = text); this.Show(); this.Refresh(); } 

A volte (questa chiamata può provenire da una varietà di thread), si verifica il seguente errore:

System.InvalidOperationException verificato System.InvalidOperationException

Message = “Invoke o BeginInvoke non può essere chiamato su un controllo finché non è stato creato l’handle della finestra.”

Source = “System.Windows.Forms”

 StackTrace: at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args) at System.Windows.Forms.Control.Invoke(Delegate method) at DriverInterface2.UI.WinForms.Dialogs.FormExtensions.SafeInvoke[T](T control, Action`1 action) in C:\code\DriverInterface2\DriverInterface2.UI.WinForms\Dialogs\FormExtensions.cs:line 16 

Cosa sta succedendo e come lo risolvo? So che non è un problema di creazione di moduli, dal momento che a volte funzionerà una volta e fallirà la volta successiva, quindi quale potrebbe essere il problema?

PS. Sono davvero pessimo con WinForms, qualcuno conosce una buona serie di articoli che spiega l’intero modello e come lavorarci?

È ansible che tu stia creando i tuoi controlli sulla discussione sbagliata. Si consideri la seguente documentazione da MSDN :

Ciò significa che InvokeRequired può restituire false se Invoke non è richiesto (la chiamata si verifica sullo stesso thread) o se il controllo è stato creato su un thread diverso ma l’handle del controllo non è stato ancora creato.

Nel caso in cui l’handle del controllo non sia stato ancora creato, non si devono semplicemente chiamare proprietà, metodi o eventi sul controllo. Ciò potrebbe causare la creazione dell’impugnatura del controllo sul thread in background, isolando il controllo su un thread senza un pump dei messaggi e rendendo l’applicazione instabile.

È ansible proteggere da questo caso controllando anche il valore di IsHandleCreated quando InvokeRequired restituisce false su un thread in background. Se l’handle di controllo non è stato ancora creato, è necessario attendere fino a quando non è stato creato prima di chiamare Invoke o BeginInvoke. In genere, ciò si verifica solo se viene creato un thread in background nel costruttore del modulo primario per l’applicazione (come in Application.Run (nuovo MainForm ()), prima che il modulo sia stato mostrato o Application.Run sia stato chiamato.

Vediamo cosa significa questo per te. (Sarebbe più semplice ragionare se abbiamo visto anche l’implementazione di SafeInvoke)

Supponendo che la tua implementazione sia identica a quella di riferimento con l’eccezione del controllo contro IsHandleCreated , seguiamo la logica:

 public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous) { if (uiElement == null) { throw new ArgumentNullException("uiElement"); } if (uiElement.InvokeRequired) { if (forceSynchronous) { uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); } else { uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); } } else { if (uiElement.IsDisposed) { throw new ObjectDisposedException("Control is already disposed."); } updater(); } } 

Considera il caso in cui chiamiamo SafeInvoke dal thread non-gui per un controllo il cui handle non è stato creato.

uiElement non è nullo, quindi controlliamo uiElement.InvokeRequired . Per i documenti MSDN (in grassetto) InvokeRequired restituirà false perché, anche se è stato creato su un thread diverso, l’handle non è stato creato! Questo ci invia alla condizione else cui controlliamo IsDisposed o procediamo immediatamente a chiamare l’azione inviata … dal thread in background !

A questo punto, tutte le scommesse sono off re: quel controllo perché il suo handle è stato creato su un thread che non ha un pump di messaggi per esso, come menzionato nel secondo paragrafo. Forse è questo il caso che stai incontrando?

Ho trovato l’ InvokeRequired non affidabile, quindi lo uso semplicemente

 if (!this.IsHandleCreated) { this.CreateHandle(); } 

Ecco la mia risposta a una domanda simile:

Penso (non ancora del tutto sicuro) che ciò sia dovuto al fatto che InvokeRequired restituirà sempre false se il controllo non è ancora stato caricato / mostrato. Ho fatto una soluzione che sembra funzionare per il momento, che è di riferirsi semplicemente all’handle del controllo associato nel suo creatore, in questo modo:

 var x = this.Handle; 

(Vedi http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html )

Il metodo nel post che si collega chiama Invoke / BeginInvoke prima di verificare se l’handle del controllo è stato creato nel caso in cui è stato chiamato da un thread che non ha creato il controllo.

Quindi otterrai l’eccezione quando il tuo metodo viene chiamato da un thread diverso da quello che ha creato il controllo. Ciò può accadere da eventi remoti o elementi utente di lavoro in coda …

MODIFICARE

Se controlli InvokeRequired e HandleCreated prima di chiamare invoke, non dovresti ottenere quell’eccezione.

Se si intende utilizzare un Control da un altro thread prima di mostrare o eseguire altre operazioni con il Control , prendere in considerazione l’opportunità di forzare la creazione del relativo handle all’interno del costruttore. Questo viene fatto usando la funzione CreateHandle .

In un progetto a più thread, in cui la logica “controller” non è in un WinForm, questa funzione è strumentale nei costruttori di Control per evitare questo errore.

Fai riferimento all’handle del controllo associato nel suo creatore, in questo modo:

Nota : fai attenzione a questa soluzione. Se un controllo ha una maniglia, è molto più lento fare cose come impostarne la dimensione e la posizione. Ciò rende InitializeComponent molto più lento. Una soluzione migliore consiste nel non fare nulla sullo sfondo prima che il controllo abbia una maniglia.

Stavo vivendo lo stesso errore. Stavo chiamando il richiamo dal costruttore di Form ().

Ho risolto questo problema richiamando invece il richiamo dall’evento Form_Load.

Non ho visto eccezioni dopo aver apportato questa modifica.

Ho avuto questo problema con questo tipo di forma semplice:

 public partial class MyForm : Form { public MyForm() { Load += new EventHandler(Form1_Load); } private void Form1_Load(Object sender, EventArgs e) { InitializeComponent(); } internal void UpdateLabel(string s) { Invoke(new Action(() => { label1.Text = s; })); } } 

Quindi per altri thread asincroni stavo usando il new MyForm().UpdateLabel(text) per provare a chiamare il thread dell’interfaccia utente, ma il costruttore non dà alcun handle all’istanza del thread UI, quindi gli altri thread ottengono altri handle di istanza, che sono o Object reference not set to an instance of an object o Invoke or BeginInvoke cannot be called on a control until the window handle has been created . Per risolvere questo ho usato un object statico per contenere la maniglia dell’interfaccia utente:

 public partial class MyForm : Form { private static MyForm _mf; public MyForm() { Load += new EventHandler(Form1_Load); } private void Form1_Load(Object sender, EventArgs e) { InitializeComponent(); _mf = this; } internal void UpdateLabel(string s) { _mf.Invoke((MethodInvoker) delegate { _mf.label1.Text = s; }); } } 

Immagino che stia funzionando bene, finora …