Evitare di chiamare Invoke quando il controllo è eliminato

Ho il seguente codice nel mio thread di lavoro ( ImageListView seguito è derivato da Control ):

 if (mImageListView != null && mImageListView.IsHandleCreated && !mImageListView.IsDisposed) { if (mImageListView.InvokeRequired) mImageListView.Invoke( new RefreshDelegateInternal(mImageListView.RefreshInternal)); else mImageListView.RefreshInternal(); } 

Tuttavia, a volte ObjectDisposedException con il metodo Invoke sopra. Sembra che il controllo possa essere eliminato tra il momento in cui controllo IsDisposed e chiamo Invoke . Come posso evitarlo?

Ci sono condizioni di gara implicite nel tuo codice. Il controllo può essere disposto tra il test IsDisposed e il test InvokeRequired. Ce n’è un altro tra InvokeRequired e Invoke (). Non è ansible risolvere questo problema senza garantire che il controllo sopravviva alla vita del thread. Dato che il thread sta generando dati per una visualizzazione elenco, è necessario interrompere l’esecuzione prima che la visualizzazione elenco scompaia.

Fatelo impostando e.Cancel nell’evento FormClosing e segnalando che il thread si arresta con un ManualResetEvent. Al termine del thread, chiama nuovamente Form.Close (). Utilizzando BackgroundWorker è facile implementare la logica di completamento del thread, trovare il codice di esempio in questo post .

Quello che hai qui è una condizione di gara . Stai meglio afferrando l’eccezione ObjectDisposed e finiscila. In effetti, penso che in questo caso sia l’ unica soluzione funzionante.

 try { if (mImageListView.InvokeRequired) mImageListView.Invoke(new YourDelegate(thisMethod)); else mImageListView.RefreshInternal(); } catch (ObjectDisposedException ex) { // Do something clever } 

Prova a usare

 if(!myControl.Disposing) ; // invoke here 

Ho avuto lo stesso problema di te. Da quando sono passato al controllo .Disposizioni sul controllo, ObjectDisposedException è andato via. Non dire questo lo aggiusterà il 100% delle volte, solo il 99%;) C’è ancora una possibilità di una race condition tra il check to Disposing e la call to invoke, ma nel test che ho fatto non ho corso in esso (io uso il ThreadPool e un thread di lavoro).

Ecco cosa uso prima di ogni chiamata da richiamare:

  private bool IsControlValid(Control myControl) { if (myControl == null) return false; if (myControl.IsDisposed) return false; if (myControl.Disposing) return false; if (!myControl.IsHandleCreated) return false; if (AbortThread) return false; // the signal to the thread to stop processing return true; } 

La realtà è che con Invoke e gli amici, non è ansible proteggere completamente contro invocare su un componente dismesso, o quindi ottenere InvalidOperationException a causa dell’handle mancante. Non ho ancora visto una risposta, come quella più in basso, in nessuno dei thread che risolvono il vero problema fondamentale, che non può essere completamente risolto con i test preventivi o usando la semantica del lock.

Ecco il normale idioma “corretto”:

 // the event handler. in this case preped for cross thread calls void OnEventMyUpdate(object sender, MyUpdateEventArgs e) { if (!this.IsHandleCreated) return; // ignore events if we arn't ready, and for // invoke if cant listen to msg queue anyway if (InvokeRequired) Invoke(new MyUpdateCallback(this.MyUpdate), e.MyData); else this.MyUpdate(e.MyData); } // the update function void MyUpdate(Object myData) { ... } 

Il problema fondamentale:

Nell’utilizzo della funzione Invoke viene utilizzata la coda dei messaggi di Windows, che inserisce un messaggio nella coda in attesa o ignora la chiamata incrociata esattamente come Post o Invia messaggio. Se c’è un messaggio prima del messaggio Invoke che invalida il componente e il relativo handle di finestra, o che è stato inserito subito dopo i controlli che si tenta di eseguire, si avrà un brutto momento.

  x thread -> PostMessage(WM_CLOSE); // put 'WM_CLOSE' in queue y thread -> this.IsHandleCreated // yes we have a valid handle y thread -> this.Invoke(); // put 'Invoke' in queue ui thread -> this.Destroy(); // Close processed, handle gone y thread -> throw Invalid....() // 'Send' comes back, thrown on calling thread y 

Non esiste un modo reale per sapere che il controllo sta per rimuoversi dalla coda e nulla di veramente ragionevole si può fare per “annullare” il richiamo. Indipendentemente dal numero di controlli effettuati o di blocchi aggiuntivi, non è ansible impedire a qualcun altro di emettere qualcosa come chiusura o distriggerszione. Ci sono tonnellate di senari in cui ciò può accadere.

Una soluzione:

La prima cosa da capire è che il richiamo sta per fallire, non diverso da come un controllo (IsHandleCreated) avrebbe ignorato l’evento. Se l’objective è proteggere il chiamante sul thread non dell’interfaccia utente, dovrai gestire l’eccezione e trattarla come qualsiasi altra chiamata che non ha avuto successo (per impedire che l’app si arresti o fare qualsiasi cosa. reroll Invoke facility, il catch è l’unico modo per saperlo.

 // the event handler. in this case preped for cross thread calls void OnEventMyWhatever(object sender, MyUpdateEventArgs e) { if (!this.IsHandleCreated) return; if (InvokeRequired) { try { Invoke(new MyUpdateCallback(this.MyUpdate), e.MyData); } catch (InvalidOperationException ex) // pump died before we were processed { if (this.IsHandleCreated) throw; // not the droids we are looking for } } else { this.MyUpdate(e.MyData); } } // the update function void MyUpdate(Object myData) { ... } 

Il filtro delle eccezioni può essere adattato per soddisfare qualsiasi esigenza. È bene essere consapevoli del fatto che i thread di lavoro spesso non hanno tutte le attenuanti eccezioni di gestione esterna delle eccezioni e la registrazione dei thread dell’interfaccia utente, nella maggior parte delle applicazioni, quindi potresti desiderare semplicemente trucidare qualsiasi eccezione sul lato worker. Oppure registra e ricontrolla tutti. Per molte eccezioni non rilevate sul thread di lavoro significa che l’app si arresta in modo anomalo.

può essere lock (mImageListView) {…}?

Potresti usare mutex.

Da qualche parte all’inizio del thread:

  Mutex m=new Mutex(); 

Poi :

 if (mImageListView != null && mImageListView.IsHandleCreated && !mImageListView.IsDisposed) { m.WaitOne(); if (mImageListView.InvokeRequired) mImageListView.Invoke( new RefreshDelegateInternal(mImageListView.RefreshInternal)); else mImageListView.RefreshInternal(); m.ReleaseMutex(); } 

E dovunque tu stia smaltendo mImageListView:

  m.WaitOne(); mImageListView.Dispose(); m.ReleaseMutex(); 

Questo dovrebbe garantire che non puoi smaltire e invocare allo stesso tempo.

Vedi anche questa domanda:

Evitare i problemi di Invoke / BeginInvoke nella gestione degli eventi WinForm cross-thread?

La class di utilità che ha consentito a EventHandlerForControl di risolvere questo problema per le firme dei metodi di evento. Puoi adattare questa lezione o rivedere la logica in essa contenuta per risolvere il problema.

Il vero problema qui è che nobugz è corretto mentre sottolinea che le API fornite per le chiamate cross-thread in winforms non sono intrinsecamente thread-safe. Anche all’interno delle chiamate a InvokeRequired e Invoke / BeginInvoke ci sono diverse condizioni di gara che possono causare comportamenti imprevisti.

Se un BackGroundWorker è una possibilità, c’è un modo molto semplice per aggirare questo:

 public partial class MyForm : Form { private void InvokeViaBgw(Action action) { BGW.ReportProgress(0, action); } private void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e) { if (this.IsDisposed) return; //You are on the UI thread now, so no race condition var action = (Action)e.UserState; action(); } private private void BGW_DoWork(object sender, DoWorkEventArgs e) { //Sample usage: this.InvokeViaBgw(() => MyTextBox.Text = "Foo"); } } 

Gestire l’evento di chiusura del modulo. Verificare se il lavoro del thread dell’interfaccia utente non è ancora in esecuzione, in tal caso iniziare a chiuderlo, annullare l’evento di chiusura e quindi riprogrammare la chiusura utilizzando BeginInvoke sul controllo modulo.

 private void Form_FormClosing(object sender, FormClosingEventArgs e) { if (service.IsRunning) { service.Exit(); e.Cancel = true; this.BeginInvoke(new Action(() => { this.Close(); })); } } 

La soluzione proposta da Isak Savo

 try { myForm.Invoke(myForm.myDelegate, new Object[] { message }); } catch (ObjectDisposedException) { //catch exception if the owner window is already closed } 

funziona in C # 4.0 ma per alcuni motivi fallisce in C # 3.0 (l’eccezione è comunque sollevata)

Quindi ho usato un’altra soluzione basata su un flag che indica se il modulo è in chiusura e di conseguenza impedisce l’uso di invoke se il flag è impostato

  public partial class Form1 : Form { bool _closing; public bool closing { get { return _closing; } } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { _closing = true; } ... // part executing in another thread: if (_owner.closing == false) { // the invoke is skipped if the form is closing myForm.Invoke(myForm.myDelegate, new Object[] { message }); } 

Questo ha il vantaggio di evitare completamente l’uso di try / catch.

Un modo potrebbe essere quello di chiamare più il metodo stesso anziché invocare il metodo ImageListView:

 if (mImageListView != null && mImageListView.IsHandleCreated && !mImageListView.IsDisposed) { if (mImageListView.InvokeRequired) mImageListView.Invoke(new YourDelegate(thisMethod)); else mImageListView.RefreshInternal(); } 

In questo modo verrebbe controllato ancora una volta prima di chiamare RefreshInternal ().

Il suggerimento di interrompere il thread che genera i messaggi non è accettabile. I delegati possono essere multicast. Poiché un ascoltatore non vuole ascoltare la band, non spari ai membri della band. Dal momento che il framework non fornisce alcun modo semplice che conosca per cancellare il messaggio pump di quei messaggi di evento, e dal momento che il modulo non espone la sua proprietà privata che ci fa sapere che il modulo sta chiudendo: Imposta un flag sull’evento IsClosing di la finestra dopo aver annullato l’iscrizione o interrotto l’ascolto degli eventi, e controlla sempre questo flag prima di eseguire una this.Invoke ().

ho lo stesso errore il mio errore si è verificato nella discussione. finalmente scrivo questo metodo:

 public bool IsDisposed(Control ctrl) { if (ctrl.IsDisposed) return true; try { ctrl.Invoke(new Action(() => { })); return false; } catch (ObjectDisposedException) { return true; } } 

Questo funziona per me

 if (this.IsHandleCreated){ Task.Delay(500).ContinueWith(_ =>{ this.Invoke(fm2); }); } else { this.Refresh(); }