2013-08-02 11 views
7

Ho cercato di aggiungere qualche stringa in una certa ListBox che ho sulla mia domanda (semplice winform) - e l'ho fatto nel usando BeginInbokeperché non è presente EndInvoke nella chiamata del componente dell'interfaccia utente cross-thread?

myListBox.BeginInvoke(new Action(delegate() 
{ 
     myListBox.Items.Add("some string")); 
})); 

Dopo che rileggendo quei 3 linee - e io don Non capisco perché in qualsiasi esempio di interfaccia utente di thread incrociati che guardo su google e su MSDN non vedo alcuna chiamata di EndInvoke? C'è qualche motivo per non chiamare EndInvoke in questo caso?

risposta

16

Questa è stata una sfortunata scelta di denominazione in .NET. I metodi Control.BeginInvoke e Dispatcher.BeginInvoke hanno lo stesso nome dei metodi di un delegato ma operano completamente diversi. Le differenze principali: type-safe

  • metodo BeginInvoke() di un delegato è sempre, ha gli stessi argomenti esatte come la dichiarazione delegato. Questo manca completamente dalle versioni Control/Dispatcher, gli argomenti sono passati attraverso una matrice params di tipo oggetto []. Il compilatore non ti dirà quando ottieni una discussione sbagliata, bombarda in fase di esecuzione

  • Un metodo Invoke() di un delegato esegue la destinazione delegata sullo stesso thread. Non è il caso per Control/Dispatcher.Invoke(), effettua il marshalling della chiamata al thread dell'interfaccia utente

  • Un'eccezione che viene lanciata nella destinazione BeginInvoke() di un delegato viene catturata e non causa il fallimento del programma. Da ri-generare quando si chiama EndInvoke(). Questo non è assolutamente il caso per Control/Dispatcher.BeginInvoke(), che genera l'eccezione sul thread dell'interfaccia utente. Senza un modo decente di rilevare l'eccezione, uno dei motivi più importanti dell'esistenza di Application.UnhandledException.

  • Chiamare il metodo EndInvoke() di un delegato è richiesto, causa una perdita di risorse di 10 minuti se non lo si fa. È non necessario per i metodi Control/Dispatcher.BeginInvoke() e in pratica non lo si fa mai.

  • Utilizzo di Control/Dispatcher.Invoke() è rischioso, è abbastanza probabile che causi stallo. Attivato quando il thread dell'interfaccia utente non è pronto per richiamare il target e fa qualcosa di poco saggio come in attesa del completamento di un thread. Non è un problema per un delegato, non perché il suo metodo Invoke() non usa un thread.

  • Calling Control/Dispatcher.BeginInvoke() sul thread UI è uno scenario supportato. La destinazione viene ancora eseguita sul thread dell'interfaccia utente, come previsto. Ma più tardi, dopo che il thread dell'interfaccia utente torna di nuovo inattivo e rientra nel ciclo del dispatcher. Questa è in realtà una funzionalità molto utile, aiuta a risolvere problemi di rientro. In particolare nei gestori di eventi per i controlli dell'interfaccia utente che si comportano male quando si esegue il codice con troppi effetti collaterali.

Una grande lista con dettagli di implementazione pesanti. La versione TLDR è certamente: "Non hanno nulla in comune, non chiamare EndInvoke è soddisfacente e del tutto normale".

+2

Ottima risposta! La lista di confronto tra 'Control. ... Invoke' e' TDelegate. ... Invoke' dovrebbe essere su MSDN. - Ti suggerisco solo di spiegare su 'Dispatcher' di WPF/Silverlight in una nota a margine, poiché non esiste in Windows Form (che è il contesto di questa domanda). – stakx

+0

Grazie per questo - Ho passato a Control.cs chiedendo come risolvere una coda null: threadCallBackList. Ora posso riposare facilmente e rimuovere EndInvoke che causa il problema! :) –

5

Control.BeginInvoke non sembra seguire completamente il consueto modello BeginX/EndX a.k.a Asynchronous Programming Model (APM). Di solito, si deve chiamata EndX per ogni BeginX, ma nel caso di Control.BeginInvoke, questo non è strettamente necessario:

"Tu può chiamare EndInvoke per recuperare il valore restituito dal delegato, se neccesary, ma questo è non richiesto. EndInvoke bloccherà fino a quando il valore di ritorno può essere recuperato. "

— dalla sezione Osservazioni sul MSDN reference page for Control.BeginInvoke (enfasi da me)

E in pratica, non è quasi mai necessario. Questo perché il metodo viene solitamente chiamato per consentire l'esecuzione di alcuni codici sul thread dell'interfaccia utente che aggiorna l'interfaccia utente. L'aggiornamento dell'interfaccia utente normalmente non produrrà alcun valore di ritorno, pertanto non si desidera chiamare EndInvoke.

+0

Eliminato il mio post come quando l'ho modificato alcune volte, ha finito per sembrare molto simile al tuo :) –

+0

Da quello che so - se non chiami EndInvoke per chiudere IAsyncResult che è stato restituito da BeginInvoke - otterrò una perdita di memoria - correggimi se ho torto – Yanshof

+2

@Yanshof: In questo caso specifico (Windows Forms 'Control.BeginInvoke'), non c'è alcuna indicazione di perdita di memoria quando non si chiama 'Control.EndInvoke '. Dopo tutto, questa è una pratica diffusa e un modello ufficialmente documentato. Se causava perdite di memoria, qualcuno sarebbe stato costretto a notarlo qualche volta durante i molti anni dell'esistenza di Windows Form. – stakx

Problemi correlati