La risposta a questa domanda sta nel modo in cui funzionano i controlli C #
I controlli in Windows Form sono associati a un thread specifico e non sono thread-safe. Pertanto, se si chiama il metodo di un controllo da un thread diverso, è necessario utilizzare uno dei metodi invoke del controllo per effettuare il marshalling della chiamata al thread corretto. Questa proprietà può essere utilizzata per determinare se è necessario chiamare un metodo invoke, che può essere utile se non si sa quale thread possiede un controllo.
Da Control.InvokeRequired
In effetti, ciò che Invoke fa è assicurarsi che il codice che si sta chiamando si trovi sul thread su cui "risiede" il controllo, prevenendo efficacemente le eccezioni con thread incrociati.
Da una prospettiva storica, in .Net 1.1, questo era effettivamente consentito. Ciò significava che si poteva provare ad eseguire codice sul thread "GUI" da qualsiasi thread in background e questo avrebbe funzionato principalmente. A volte causava la chiusura della tua app perché stavi effettivamente interrompendo il thread della GUI mentre stava facendo qualcos'altro. Questa è l' eccezione Cross Threaded : immagina di provare ad aggiornare un TextBox mentre la GUI sta dipingendo qualcos'altro.
- Quale azione ha la priorità?
- È anche possibile che avvengano entrambi contemporaneamente?
- Cosa succede a tutti gli altri comandi che la GUI deve eseguire?
In effetti, stai interrompendo una coda, che può avere molte conseguenze impreviste. Invoke è effettivamente il modo "educato" per ottenere ciò che si desidera fare in quella coda, e questa regola è stata applicata da .Net 2.0 in poi tramite una InvalidOperationException generata .
Per capire cosa sta effettivamente succedendo dietro le quinte e cosa si intende per "thread GUI", è utile capire cos'è un Message Pump o un Message Loop.
Questo in realtà ha già una risposta nella domanda " Che cos'è un Message Pump " e si consiglia di leggere per comprendere il meccanismo effettivo a cui ci si sta collegando quando si interagisce con i controlli.
Altre letture che potresti trovare utili includono:
Che succede con Begin Invoke
Una delle regole cardinali della programmazione della GUI di Windows è che solo il thread che ha creato un controllo può accedere e / o modificarne il contenuto (ad eccezione di alcune eccezioni documentate). Prova a farlo da qualsiasi altro thread e otterrai un comportamento imprevedibile che va dal deadlock, alle eccezioni a un'interfaccia utente aggiornata a metà. Il modo corretto quindi per aggiornare un controllo da un altro thread è inserire un messaggio appropriato nella coda dei messaggi dell'applicazione. Quando il message pump arriva a eseguire quel messaggio, il controllo verrà aggiornato, sullo stesso thread che lo ha creato (ricorda, il message pump viene eseguito sul thread principale).
e, per una panoramica più ricca di codice con un campione rappresentativo:
Operazioni cross-thread non valide
// the canonical form (C# consumer)
public delegate void ControlStringConsumer(Control control, string text); // defines a delegate type
public void SetText(Control control, string text) {
if (control.InvokeRequired) {
control.Invoke(new ControlStringConsumer(SetText), new object[]{control, text}); // invoking itself
} else {
control.Text=text; // the "functional part", executing only on the main thread
}
}
Una volta che hai apprezzato InvokeRequired, potresti prendere in considerazione l'utilizzo di un metodo di estensione per concludere queste chiamate. Questo è abilmente trattato nella domanda Stack Overflow Pulizia del codice disseminato di richiesta di richiamo .
C'è anche un ulteriore resoconto di ciò che è accaduto storicamente che potrebbe essere di interesse.