In che modo il supporto asincrono C # 5 aiuta i problemi di sincronizzazione dei thread dell'interfaccia utente?

16

Ho sentito da qualche parte che il C # 5 asincrono sarà così fantastico che non dovrai preoccuparti di fare questo:

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

Sembra che il callback di un'operazione di attesa si verifichi nel thread originale del chiamante. È stato più volte affermato da Eric Lippert e Anders Hejlsberg che questa caratteristica è nata dalla necessità di rendere le UI (in particolare le UI del dispositivo touch) più reattive.

Penso che un uso comune di tale funzione sia simile a questo:

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

Se viene utilizzata solo una richiamata, l'impostazione del testo dell'etichetta genera 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? Ci sono documenti che spiegano tecnicamente come funzionerà?

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

    
posta Alex 16.10.2011 - 17:51
fonte

3 risposte

17

Penso che stai confondendo alcune cose, qui. Quello che stai chiedendo è già possibile usando System.Threading.Tasks , il async e await in C # 5 forniranno un po 'più sintetico zucchero sintattico per la stessa funzione.

Usiamo un esempio di Winforms - rilascia un pulsante e una casella di testo sul modulo e utilizza 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 "cross-thread operation not valid" - a meno che non elimini TaskScheduler argomento dell'ultimo ContinueWith , nel qual caso lo farai.

Questo è stile di passaggio continuo . La magia si verifica nella classe TaskScheduler e in particolare l'istanza recuperata da FromCurrentSynchronizationContext . Passa a questo in qualsiasi continuazione e dici che la continuazione deve essere eseguita su qualsiasi thread chiamato il metodo FromCurrentSynchronizationContext - 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 continuare la continuazione. Quindi il codice sopra può essere scritto piccolo 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 infatti sono molto simili. Il metodo DelayedAddAsync ora restituisce un Task<int> invece di un int , quindi il await è solo uno schiaffo di continuazioni su ciascuno di questi. La differenza principale è che passa il contesto di sincronizzazione su ogni riga, quindi non devi farlo esplicitamente come nell'ultimo esempio.

In teoria le differenze sono molto più significative. Nel secondo esempio, ogni singola riga nel metodo button1_Click 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 per l'assegnazione a textBox1.Text che abbiamo esplicitamente allegato al thread dell'interfaccia utente contesto di sincronizzazione.

Questo è ciò che è veramente interessante su await - il fatto che un cameriere sia in grado di entrare e uscire dallo stesso metodo senza alcuna chiamata di blocco. Chiama await , il thread corrente ritorna ai messaggi di elaborazione e, una volta terminato, l'utente attenderà esattamente da dove era stato interrotto, nello stesso thread in cui lo aveva interrotto. Ma in termini di Invoke / BeginInvoke contrasto nella domanda, mi dispiace dire che avresti dovuto smettere di farlo molto tempo fa.

    
risposta data 16.10.2011 - 20:16
fonte
4

Sì, per il thread dell'interfaccia utente, il callback di un'operazione di attesa si verificherà sul thread originale del chiamante.

Eric Lippert ha scritto una serie in 8 parti tutto su di esso un anno fa: Favolose avventure nella codifica

EDIT: ed ecco Anders '// build / presentation: channel9

A proposito, avete notato che se girate "// build /" sottosopra, ottenete "/ plinq //"; -)

    
risposta data 16.10.2011 - 20:00
fonte
3

Jon Skeet ha dato una presentazione eccellente sui metodi Async in C # 5 che potresti trovare estremamente utile:

link

    
risposta data 17.12.2011 - 02:16
fonte

Leggi altre domande sui tag