2012-10-23 16 views
7

Vorrei conoscere il ragionamento che spiega il modo in cui il compilatore sceglie TaskScheduler durante la compilazione utilizzando la parola chiave async.parola chiave asincrona e scelta del TaskScheduler

Il metodo di test è chiamato da SignalR (host ASP.NET, IIS8, trasporto websocket) sul metodo OnConnectedAsync.

protected override async Task OnConnectedAsync(IRequest request, string connectionId) 
{ 
    SendUpdates(); 
} 

partire un processo sul contesto sincronizzazione corrente provocherà un InvalidOperationException in System.Web.AspNetSynchronizationContext.OperationStarted()

Un'operazione asincrono non può essere avviato in questo momento. Le operazioni asincrone possono essere avviate solo all'interno di un gestore o modulo asincrono o durante determinati eventi nel ciclo di vita della pagina. Se si è verificata questa eccezione durante l'esecuzione di una pagina, assicurarsi che la pagina sia contrassegnata con <%@ Page Async="true" %>.

Fine. Con questa definizione SendUpdates, ottengo l'eccezione di cui sopra:

private async void SendUpdates() 
    { 
     Task.Run(async() => 
      { 
       while (true) 
       { 
        await Task.Delay(1000); 
        await Connection.Broadcast("blabla"); 
       } 
      }); 

    } 

Ma ancora più interessante è quando non ottengo l'eccezione. Le seguenti opere:

private void SendUpdates() 

e le seguenti opere troppo

private async Task SendUpdates() 

quest'ultimo funziona troppo, ma è essenzialmente lo stesso come l'esempio di cui sopra.

private Task SendUpdates() 
    { 
     return Task.Run(async() => 
      { 
       while (true) 
       { 
        await Task.Delay(1000); 
        await Connection.Broadcast("blabla"); 
       } 
      }); 

    } 

Sai come il compilatore sceglie quale programma di pianificazione utilizzare qui?

risposta

10

Una delle linee guida principali per iscritto async codice è "evitare async void" - cioè, utilizzare async Task invece di async void meno che non si sta implementando un gestore async eventi.

async void metodi utilizzano SynchronizationContext 's OperationStarted e OperationCompleted; vedere il mio articolo MSDN It's All about the SynchronizationContext per ulteriori dettagli.

ASP.NET rileva la chiamata a OperationStarted e (correttamente) la rifiuta perché è illegale inserire un gestore di eventi async. Quando si corregge il codice per utilizzare async Task, quindi ASP.NET non vede più un gestore di eventi async.

È possibile trovare il mio intro to async/await post utile.

+0

Questa è la risposta, grazie – Xin

3

quando si chiama:

private async void SendUpdates() 

con la chiamata a Task.Run e utilizzando il async keyword sulla delegato anonimo, non sono in realtà fornendo una continuazione; si avvia il Task e si sta dando al metodo Run una continuazione, che viene quindi elaborata. Questa continuazione non è canalizzata in alcun modo significativo al codice che ha chiamato Task.Run.

Ecco perché si ottiene l'eccezione, il gestore non sa al await sul Task che la chiamata alla Task.Run produce.

Detto questo:

private void SendUpdates() 

funziona perché il compito è stato creato e il codice non cattura un SynchronizationContext (perché non c'è async parola chiave sul metodo, Task casi non catturano di default) . Stai licenziando il compito, ma è fuoco e dimentichi.

e la seguente funziona anche:

private async Task SendUpdates() 

Vale a dire perché nella restituzione del Task, hai restituito un awaitable che la richiamata può lavorare.

Per rispondere alla tua domanda direttamente, il compilatore si assicurerà di ottenere il SynchronizationContext restituito da SynchronizationContext.Current prima di chiamare await; qualunque continuazione venga chiamata dopo che i ritorni attendibili saranno chiamati usando quello SynchronizationContext.

+0

In effetti, non voglio aspettare nulla. Intendi dire che quando scrivo Task SendUpdates() asincrono, la mia attività interna è racchiusa in un'attività compatibile con il contesto di sincronizzazione ASP.NET, ma che quando chiamo async void SendUpdates(), questo involucro non accade, il che spiega il problema? – Eilistraee

+0

@Eilistraee Quando scrivi 'async Task SendUpdates', sì,' SynchronizationContext' viene catturato e l''Task' che viene restituita è attesa. Quando hai 'async void SendUpdates', non stai invocando nulla. Se mai, dovresti avere almeno un avvertimento del compilatore che indica che stai usando 'async' ma non hai un' await' corrispondente (l'attesa è sul delegato anonimo, non nel codice di chiamata effettivo del metodo). – casperOne

+0

Ma anche nel caso SendUpdates() task asincrono, non sono in attesa dell'attività restituita. Voglio dire, questo riferimento di attività è perso e OnConnectedAsync continua la sua esecuzione senza attendere nulla (ed è una buona cosa dato che l'attività interna non verrà completata) – Eilistraee