In che modo il supporto asincrono C # 5 aiuterà i problemi di sincronizzazione del thread dell'interfaccia utente?


16

Ho sentito da qualche parte che C # 5 async-waitit sarà così fantastico che non dovrai preoccuparti di farlo:

if (InvokeRequired)
{
    BeginInvoke(...);
    return;
}
// do your stuff here

Sembra che il callback di un'operazione wait avvenga nel thread originale del chiamante. Eric Lippert e Anders Hejlsberg hanno affermato più volte che questa funzione è nata dalla necessità di rendere più reattive le interfacce utente (in particolare le interfacce dei dispositivi touch).

Penso che un uso comune di tale funzionalità sarebbe qualcosa del genere:

public class Form1 : Form
{
    // ...
    async void GetFurtherInfo()
    {
        var temperature = await GetCurrentTemperatureAsync();
        label1.Text = temperature;
    }
}

Se viene utilizzato solo un callback, l'impostazione del testo dell'etichetta genererà un'eccezione perché non viene eseguita nel thread dell'interfaccia utente.

Finora non sono riuscito a trovare alcuna risorsa a conferma di ciò. Qualcuno lo sa? Esistono documenti che spiegano tecnicamente come funzionerà?

Fornisci un link da una fonte affidabile, non solo rispondere "sì".


Ciò sembra altamente improbabile, almeno per quanto riguarda la awaitfunzionalità. È solo un sacco di zucchero sintattico per il passaggio di seguito . Forse ci sono altri miglioramenti non correlati a WinForms che dovrebbero aiutare? Ciò rientrerebbe tuttavia nel framework .NET stesso e non nello specifico C #.
Aaronaught,

@Aaronaught Sono d'accordo, ecco perché sto ponendo la domanda con precisione. Ho modificato la domanda per chiarire da dove vengo. Sembra strano che possano creare questa funzione e richiedono comunque che utilizziamo il famigerato stile di codice InvokeRequired.
Alex,

Risposte:


17

Penso che stai confondendo alcune cose, qui. Quello che stai chiedendo è già possibile utilizzare System.Threading.Tasks, asynce awaitin C # 5 forniranno solo un po 'di zucchero sintattico più carino per la stessa funzione.

Facciamo un esempio di Winforms: rilascia un pulsante e una casella di testo sul modulo e usa questo codice:

private void button1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew<int>(() => DelayedAdd(5, 10))
        .ContinueWith(t => DelayedAdd(t.Result, 20))
        .ContinueWith(t => DelayedAdd(t.Result, 30))
        .ContinueWith(t => DelayedAdd(t.Result, 50))
        .ContinueWith(t => textBox1.Text = t.Result.ToString(),
            TaskScheduler.FromCurrentSynchronizationContext());
}

private int DelayedAdd(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

Eseguilo e vedrai che (a) non blocca il thread dell'interfaccia utente e (b) non ottieni il solito errore "operazione cross-thread non valida" - a meno che tu non rimuova l' TaskSchedulerargomento dall'ultimo ContinueWith, in quale caso lo farai.

Questo è lo stile di passaggio di continuazione standard . La magia avviene in TaskSchedulerclasse e in particolare l'istanza recuperata da FromCurrentSynchronizationContext. Passalo in qualsiasi continuazione e dici che la continuazione deve essere eseguita su qualsiasi thread chiamato FromCurrentSynchronizationContextmetodo - in questo caso, il thread dell'interfaccia utente.

Gli spettatori sono leggermente più sofisticati, nel senso che sono consapevoli di quale thread hanno iniziato e su quale thread deve avvenire la continuazione. Quindi il codice sopra può essere scritto un po ' più naturalmente:

private async void button1_Click(object sender, EventArgs e)
{
    int a = await DelayedAddAsync(5, 10);
    int b = await DelayedAddAsync(a, 20);
    int c = await DelayedAddAsync(b, 30);
    int d = await DelayedAddAsync(c, 50);
    textBox1.Text = d.ToString();
}

private async Task<int> DelayedAddAsync(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

Questi due dovrebbero apparire molto simili e in effetti sono molto simili. Il DelayedAddAsyncmetodo ora restituisce un Task<int>invece di un int, e quindi awaitsta semplicemente schiaffeggiando continuazioni su ognuna di quelle. La differenza principale è che passa lungo il contesto di sincronizzazione su ogni riga, quindi non è necessario farlo esplicitamente come abbiamo fatto nell'ultimo esempio.

In teoria le differenze sono molto più significative. Nel secondo esempio, ogni singola riga del button1_Clickmetodo viene effettivamente eseguita nel thread dell'interfaccia utente, ma l'attività stessa ( DelayedAddAsync) viene eseguita in background. Nel primo esempio, tutto viene eseguito in background , tranne l'assegnazione a textBox1.Textcui abbiamo esplicitamente associato il contesto di sincronizzazione del thread dell'interfaccia utente.

Questo è ciò che è davvero interessante await: il fatto che un cameriere è in grado di entrare e uscire dallo stesso metodo senza bloccare le chiamate. Chiama await, il thread corrente ritorna all'elaborazione dei messaggi e, quando è finito, il cameriere riprenderà esattamente da dove era stato interrotto, nello stesso thread in cui era stato interrotto. Ma in termini di Invoke/ BeginInvokecontrasto nella domanda, io ' Mi dispiace dire che avresti dovuto smettere di farlo molto tempo fa.


È molto interessante @Aaronaught. Ero a conoscenza dello stile del passaggio di continuazione ma non ero a conoscenza di tutto questo "contesto di sincronizzazione". Esiste un documento che collega questo contesto di sincronizzazione a C # 5 async-waitit? Capisco che sia una funzionalità esistente, ma il fatto che la stiano utilizzando per impostazione predefinita sembra un grosso problema, specialmente perché deve avere alcuni impatti importanti nelle prestazioni, giusto? Ulteriori commenti a riguardo? Grazie per la tua risposta a proposito.
Alex,

1
@Alex: Per le risposte a tutte queste domande di follow-up, ti suggerisco di leggere Async Performance: Comprensione dei costi di Async e Await . La sezione "Cura del contesto" spiega come tutto ciò si riferisce al contesto di sincronizzazione.
Aaronaught,

(A proposito, i contesti di sincronizzazione non sono nuovi; sono stati nel framework dal 2.0. Il TPL li ha resi molto più facili da usare.)
Aaronaught

2
Mi chiedo perché ci siano ancora molte discussioni sull'uso dello stile InvokeRequired e la maggior parte dei thread che ho visto non menziona nemmeno i contesti di sincronizzazione. Mi avrebbe risparmiato il tempo di sollevare questa domanda ...
Alex,

2
@Alex: immagino che non stavi cercando nei posti giusti . Non so cosa dirti; ci sono ampie parti della comunità .NET che impiegano molto tempo per recuperare. Diavolo, vedo ancora alcuni programmatori che usano la ArrayListclasse nel nuovo codice. Non ho ancora esperienza con RX, me stesso. Le persone imparano ciò che devono sapere e condividono ciò che già sanno, anche se ciò che già sanno è obsoleto. Questa risposta potrebbe non essere aggiornata tra qualche anno.
Aaronaught,

4

Sì, per il thread dell'interfaccia utente, il callback di un'operazione wait verrà eseguito sul thread originale del chiamante.

Eric Lippert ha scritto una serie in 8 parti un anno fa: Fabulous Adventures In Coding

EDIT: ed ecco // build / presentazione di Anders: channel9

A proposito, hai notato che se capovolgi "// build /", ottieni "/ plinq //" ;-)


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.