Richiama (delegato)


93

Qualcuno può spiegare questa dichiarazione scritta su questo link

Invoke(Delegate):

Esegue il delegato specificato sul thread che possiede l' handle della finestra sottostante del controllo.

Qualcuno può spiegare cosa significa (specialmente quello in grassetto) non sono in grado di capirlo chiaramente


4
La risposta a questa domanda è legata alla proprietà Control.InvokeRequired - vedere msdn.microsoft.com/en-us/library/…
trattino il

Risposte:


130

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.


68

Un oggetto di controllo o finestra in Windows Form è solo un wrapper attorno a una finestra Win32 identificata da un handle (a volte chiamato HWND). La maggior parte delle operazioni eseguite con il controllo risulterà infine in una chiamata API Win32 che utilizza questo handle. L'handle è di proprietà del thread che lo ha creato (in genere il thread principale) e non deve essere manipolato da un altro thread. Se per qualche motivo hai bisogno di fare qualcosa con il controllo da un altro thread, puoi usare Invokeper chiedere al thread principale di farlo per tuo conto.

Ad esempio, se vuoi cambiare il testo di un'etichetta da un thread di lavoro, puoi fare qualcosa del genere:

theLabel.Invoke(new Action(() => theLabel.Text = "hello world from worker thread!"));

Puoi spiegare perché qualcuno dovrebbe fare qualcosa di simile? this.Invoke(() => this.Enabled = true);Qualunque cosa si thisriferisca è sicuramente nel thread corrente, giusto?
Kyle Delaney

1
@KyleDelaney, un oggetto non è "in" un thread e il thread corrente non è necessariamente il thread che ha creato l'oggetto.
Thomas Levesque

24

Se si desidera modificare un controllo, è necessario farlo nel thread in cui è stato creato il controllo. Questo Invokemetodo consente di eseguire metodi nel thread associato (il thread che possiede l'handle della finestra sottostante del controllo).

Nell'esempio seguente thread1 genera un'eccezione perché SetText1 sta tentando di modificare textBox1.Text da un altro thread. Ma in thread2, Action in SetText2 viene eseguita nel thread in cui è stato creato il TextBox

private void btn_Click(object sender, EvenetArgs e)
{
    var thread1 = new Thread(SetText1);
    var thread2 = new Thread(SetText2);
    thread1.Start();
    thread2.Start();
}

private void SetText1() 
{
    textBox1.Text = "Test";
}

private void SetText2() 
{
    textBox1.Invoke(new Action(() => textBox1.Text = "Test"));
}

Mi piace molto questo approccio, nasconde la natura dei delegati, ma è comunque una bella scorciatoia.
shytikov

6
Invoke((MethodInvoker)delegate{ textBox1.Text = "Test"; });

L'uso di System.Action che le persone suggeriscono in altre risposte funziona solo su framework 3.5+, per le versioni precedenti funziona perfettamente
Suicide Platypus

2

In termini pratici, significa che è garantito che il delegato venga invocato sul thread principale. Questo è importante perché nel caso dei controlli di Windows, se non aggiorni le loro proprietà sul thread principale, non vedi la modifica o il controllo solleva un'eccezione.

Il modello è:

void OnEvent(object sender, EventArgs e)
{
   if (this.InvokeRequired)
   {
       this.Invoke(() => this.OnEvent(sender, e);
       return;
   }

   // do stuff (now you know you are on the main thread)
}

2

this.Invoke(delegate)assicurati di chiamare il delegato l'argomento a this.Invoke()sul thread principale / thread creato.

Posso dire che una regola Thumb non accede ai controlli del modulo tranne che dal thread principale.

Possono essere utili le seguenti righe per l'utilizzo di Invoke ()

    private void SetText(string text)
    {
        // InvokeRequired required compares the thread ID of the
        // calling thread to the thread ID of the creating thread.
        // If these threads are different, it returns true.
        if (this.textBox1.InvokeRequired)
        {   
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text });
        }
        else
        {
            this.textBox1.Text = text;
        }
    }

Ci sono situazioni anche se crei un thread di Threadpool (cioè thread di lavoro) verrà eseguito sul thread principale. Non creerà un nuovo thread perché il thread principale è disponibile per l'elaborazione di ulteriori istruzioni. Quindi per prima cosa verifica se il thread in esecuzione è il thread principale utilizzando this.InvokeRequiredse restituisce true il codice corrente è in esecuzione sul thread di lavoro quindi chiama this.Invoke (d, new object [] {text});

altrimenti aggiorna direttamente il controllo dell'interfaccia utente (qui hai la garanzia di eseguire il codice sul thread principale.)


1

Significa che il delegato verrà eseguito sul thread dell'interfaccia utente, anche se si chiama tale metodo da un thread di lavoro in background o da un pool di thread. Gli elementi dell'interfaccia utente hanno affinità di thread : a loro piace parlare direttamente con un thread: il thread dell'interfaccia utente. Il thread dell'interfaccia utente è definito come il thread che ha creato l'istanza di controllo ed è quindi associato all'handle della finestra. Ma tutto questo è un dettaglio di implementazione.

Il punto chiave è: chiameresti questo metodo da un thread di lavoro in modo da poter accedere all'interfaccia utente (per modificare il valore in un'etichetta, ecc.), Poiché non ti è permesso farlo da qualsiasi altro thread diverso dal thread dell'interfaccia utente.


0

I delegati sono essenzialmente inline Actiono Func<T>. È possibile dichiarare un delegato al di fuori dell'ambito di un metodo che si sta eseguendo o utilizzando lambdaun'espressione ( =>); poiché esegui il delegato all'interno di un metodo, lo esegui sul thread che viene eseguito per la finestra / applicazione corrente che è il bit in grassetto.

Esempio Lambda

int AddFiveToNumber(int number)
{
  var d = (int i => i + 5);
  d.Invoke(number);
}

0

Significa che il delegato passato viene eseguito sul thread che ha creato l'oggetto Control (che è il thread dell'interfaccia utente).

Devi chiamare questo metodo quando la tua applicazione è multi-thread e vuoi eseguire alcune operazioni dell'interfaccia utente da un thread diverso dal thread dell'interfaccia utente, perché se provi a chiamare un metodo su un Control da un thread diverso otterrai un System.InvalidOperationException.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.