2013-04-25 10 views
7

Per quanto riguarda questo pezzo di codice:Perché sono Async callback eseguito in WPF UI filo

static async Task<string> testc() 
{ 
    Console.WriteLine("helo async " + Thread.CurrentThread.ManagedThreadId); 
    await Task.Run(() => { 
     Thread.Sleep(1000); 
     Console.WriteLine("task " + Thread.CurrentThread.ManagedThreadId); 
    }); 
    Console.WriteLine("callback "+Thread.CurrentThread.ManagedThreadId); 
    return "bob"; 
} 

static void Main(string[] args) 
{ 
    Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId); 
    testc(); 
    Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId); 
    Thread.Sleep(2000); 
    Console.ReadLine(); 
} 

ottengo il seguente output:

helo sync 10 
helo async 10 
over10 
task 11 
callback **11** 

che è ok: pezzo di codice dopo await viene eseguito in lo stesso thread del compito stesso.

Ora, se lo faccio in un'applicazione WPF:

private void Button_Click_1(object sender, RoutedEventArgs e) 
{ 
    Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId); 
    testc(); 
    Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId); 
    Thread.Sleep(2000); 
    Console.ReadLine(); 
} 

Genera seguente risultato:

helo sync 8 
helo async 8 
over8 
task 9 
callback **8** 

Dove possiamo vedere codice dopo attendono di eseguito nel thread UI. Bene, questo è grandioso perché permette di manipolare collezioni osservabili, ecc ... Ma mi stavo chiedendo "Perché?" "Come potrei fare lo stesso?" Questo è legato ad un comportamento di TaskScheduler? Questo è hard-coded in .NET Framework?

Thx per qualsiasi idea tu possa inviare.

risposta

7

Il motivo è che Task.Run acquisirà lo SynchronizationContext se è presente come è in un'app WPF all'avvio dell'attività dal thread dell'interfaccia utente. L'attività utilizzerà quindi lo SynchronizationContext per serializzare la richiamata sul thread dell'interfaccia utente. Tuttavia, se nessun contesto è disponibile come in un'app Console, la richiamata avverrà su un thread diverso.

Stephen Toub ha descritto questo in un blog entry.

BTW, fare attenzione quando si utilizza mai utilizzare Thread.Sleep in un'attività.Potrebbe causare strani comportamenti poiché un'attività potrebbe non essere legata a un thread. Utilizzare invece Task.Delay.

+0

+1 ha bisogno di più enfasi sul tuo BTW però. – Phill

2

Ma mi chiedevo "Perché?"

Hai risposto tu stesso:

Beh, questo è grande in quanto permette la manipolazione di collezioni osservabili ecc ...

Il punto di asincrona è quello di rendere più facile asincronia con cui lavorare - in modo da poter scrivere il codice "sincrono all'aspetto" che è in realtà asincrono. Ciò include spesso il desiderio di rimanere all'interno di un contesto (ad esempio un thread dell'interfaccia utente) per l'intero metodo asincrono - solo "mettere in pausa" il metodo (senza bloccare il thread dell'interfaccia utente) quando è necessario attendere qualcosa.

"Come potrei fare lo stesso?"

Non è chiaro cosa intendi qui. In sostanza, l'implementazione del modello attendibile per Task utilizza TaskScheduler.FromCurrentSynchronizationContext() per determinare quale scheduler è in grado di pubblicare la richiamata, a meno che non abbia chiamato ConfigureAwait(false) per disattivare esplicitamente questo comportamento. Ecco come lo gestisce ... che tu possa o meno "fare lo stesso" dipende esattamente da quello che stai cercando di fare.

Vedere la domanda "Cosa sono gli attendibili" nello async/await FAQ per ulteriori dettagli sul modello attendibile.

+0

Da "Come potrei fare lo stesso?" Penso che voglia dire: "come faccio a ottenere il comportamento WPF senza usare WPF?" –

+0

@ jdv-JandeVaan: Se quello era il significato, è estremamente poco chiaro. Comunque, spero che i dettagli che ho dato forniranno informazioni sufficienti per portare ulteriormente l'OP. Possono sempre chiedere maggiori dettagli se necessario. –

+0

Ok .. Bene. Non voglio davvero fare lo stesso. Mi stavo chiedendo come viene inviata la richiamata al metodo Dispatcher.Invoke ... e se potessi, alla fine, fare una cosa simile o se fosse difficile codificarla nel Framework. Thx per la tua risposta – Kek

0

È possibile trovare il mio async/await intro utile. Le altre risposte sono quasi corrette.

Quando si await un Task che non ha ancora completato, di default un "contesto" viene catturato, che viene utilizzato per riprendere il metodo quando il Task è completa. Questo "contesto" è SynchronizationContext.Currenta meno che non sia nullo, nel qual caso è TaskScheduler.Current.

Nota le condizioni richieste per questo lavoro:

  • "Quando await ..." - se si pianifica una continuazione manualmente, per esempio, con Task.ContinueWith, senza la cattura contesto è fatto. Devi farlo da solo usando qualcosa come (SynchronizationContext.Current == null ? TaskSchedler.Current : TaskScheduler.FromCurrentSynchronizationContext()).
  • "... await un Task ..." - questo comportamento fa parte del comportamento awaitper Task tipi. Altri tipi possono o non possono fare una cattura simile.
  • "... che non ha ancora completato ..." - se il Task è già completo per il momento si tratta di await ndr, il metodo async continuerà sincrono. Quindi non c'è bisogno di catturare il contesto in quel caso.
  • "... per impostazione predefinita ..." - questo è il comportamento predefinito e può essere modificato. In particolare, chiamare il metodo Task.ConfigureAwait e passare false per il parametro continueOnCapturedContext. Questo metodo restituisce un tipo attendibile (non uno Task) che non continuerà nel contesto acquisito se il suo parametro era false.
+0

Thx per questa risposta completa. La maggior parte (o almeno abbastanza per rispondere alla mia domanda) era nel link nella risposta di @Jakob Christensen, ma io apprezzo questo punto di vista completo! – Kek

Problemi correlati