2015-08-12 18 views
5

Ho un metodo a mio avviso modelloDifferenza tra chiamando un metodo asincrono e Task.Run un asincrona metodo

private async void SyncData(SyncMessage syncMessage) 
{ 
    if (syncMessage.State == SyncState.SyncContacts) 
    { 
     this.SyncContacts(); 
    } 
} 

private async Task SyncContacts() 
{ 
    foreach(var contact in this.AllContacts) 
    { 
     // do synchronous data analysis 
    } 

    // ... 

    // AddContacts is an async method 
    CloudInstance.AddContacts(contactsToUpload); 
} 

Quando chiamo SyncData dai comandi di interfaccia utente e sto sincronizzazione di un grande pezzo di UI dati congela. Ma quando chiamo SyncContacts con questo approccio

private void SyncData(SyncMessage syncMessage) 
{ 
    if (syncMessage.State == SyncState.SyncContacts) 
    { 
     Task.Run(() => this.SyncContacts()); 
    } 
} 

tutto bene. Non dovrebbero essere uguali? Stavo pensando che non usare attendere per chiamare un metodo asincrono crea un nuovo thread.

+2

'async' non significa sempre che siano coinvolti altri thread - vedi [non c'è thread] (http://blog.stephencleary.com/2013/11/there-is-no-thread.html). –

+0

In SyncData manca la parola chiave await, che indica al compilatore di continuare con un'altra elaborazione. Al momento il metodo SyncData dovrebbe bloccarsi, che è corretto –

risposta

8

Non dovrebbero essere uguali? Stavo pensando che non usare Attendere per chiamando un metodo asincrono crea un nuovo thread.

No, async non assegna magicamente un nuovo thread per il suo richiamo del metodo. async-await sfrutta principalmente le API asincrone naturalmente, ad esempio una chiamata di rete a un database o un servizio Web remoto.

Quando si utilizza Task.Run, si utilizza in modo esplicito un thread del pool di thread per eseguire il delegato. Se si contrassegna un metodo con la parola chiave async, ma non si esegue internamente alcuno await, verrà eseguito in modo sincrono.

Non sono sicuro di quale sia il tuo metodo SyncContacts() in realtà (dal momento che non ne hai fornito l'implementazione), ma contrassegnandolo con lo async da solo non otterrai nulla.

Edit:

Ora che hai aggiunto l'attuazione, vedo due cose:

  1. io non sono sicuro di come la CPU è la vostra analisi dei dati sincrona, ma può essere abbastanza per l'interfaccia utente per non rispondere.
  2. Non stai aspettando la tua operazione asincrona. Ha bisogno di simile a questa:

    private async Task SyncDataAsync(SyncMessage syncMessage) 
    { 
        if (syncMessage.State == SyncState.SyncContacts) 
        { 
         await this.SyncContactsAsync(); 
        } 
    } 
    
    private Task SyncContactsAsync() 
    { 
        foreach(var contact in this.AllContacts) 
        { 
         // do synchronous data analysis 
        } 
    
        // ... 
    
        // AddContacts is an async method 
        return CloudInstance.AddContactsAsync(contactsToUpload); 
    } 
    
+0

Sei sicuro che usi esplicitamente il pool di thread? AFAIK, utilizza esplicitamente il 'TaskScheduler' di default e questo _happens_ per racchiudere il pool di thread. Potremmo saperlo, ma le astrazioni mirano a nascondere questo dettaglio e non garantisce che questo non cambierà nella prossima versione di .net. – Gusdor

+4

@Gusdor ['Task.Run'] (http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,89fc01f3bb88eed9) il valore predefinito è [' TaskScheduler.Default'] (http: // referencesource.microsoft.com/#mscorlib/system/threading/Tasks/TaskScheduler.cs,d4a38cb3e3085518), che è l'utilità di pianificazione del processo di threadpool. –

+0

@Gusdor, Yuval è corretto. Da MSDN: "Accoda il lavoro specificato da eseguire sul ThreadPool e restituisce un'attività o Task per quel lavoro." –

0

Che la vostra linea Task.Run(() => this.SyncContacts()); realtà non è la creazione di una nuova attività partendo e tornando al chiamante (che non viene utilizzato per qualsiasi ulteriori finalità nel tuo caso). Questo è il motivo per cui farà il suo lavoro in background e l'interfaccia utente continuerà a funzionare. Se è necessario (a) attendere il completamento dell'attività, è possibile utilizzare await Task.Run(() => this.SyncContacts());. Se si desidera solo assicurarsi che SyncContacts sia terminato quando si restituisce il metodo SyncData, è possibile utilizzare l'attività di restituzione e attendere alla fine del metodo SyncData. Come è stato suggerito nei commenti: se non ti interessa sapere se l'attività è finita o meno, puoi semplicemente restituirla.

Tuttavia, Microsoft consiglia di non combinare codice di blocco e codice asincrono e che i metodi asincroni terminano con Async (https://msdn.microsoft.com/en-us/magazine/jj991977.aspx). Pertanto, è necessario prendere in considerazione la ridenominazione dei metodi e non contrassegnare i metodi con async, quando non si utilizza la parola chiave await.

+0

Questo è sbagliato. 'await' non ha nulla a che fare con se qualcosa viene eseguito in background o meno. Ha a che fare con il modo in cui gestisci l'attesa del completamento dell'attività e il modo in cui gestisci la sua risposta. MS sconsiglia di usare "attendere" in tutti i casi perché introduce un sovraccarico. Se un metodo che restituisce un'attività non ha bisogno di attendere il completamento dell'attività, non c'è motivo di usare 'async/await', basta restituire l'attività come è –

+1

Non ho detto che l'attesa avrebbe eseguito attività in background. Ho detto che Task.Run() lo farà. Con la seconda cosa, hai ragione, dovrei dire che MS raccomanda di non mescolare codice di blocco e codice asincrono https://msdn.microsoft.com/en-us/magazine/jj991977.aspx. Ad ogni modo, restituire un compito al posto del vuoto dovrebbe essere assolutamente fatto. – Daniel

0

Giusto per chiarire perché l'interfaccia utente si blocca: il lavoro svolto nel ciclo stretto foreach è probabilmente limitato dalla CPU e bloccherà il thread del chiamante originale fino al completamento del ciclo.

Quindi, indipendentemente dal fatto che il Task tornato da SyncContacts è await ndr o no, la CPU bound lavoro prima di chiamare AddContactsAsync si verificherà ancora in modo sincrono, e blocco, filo del chiamante.

private Task SyncContacts() 
{ 
    foreach(var contact in this.AllContacts) 
    { 
     // ** CPU intensive work here. 
    } 

    // Will return immediately with a Task which will complete asynchronously 
    return CloudInstance.AddContactsAsync(contactsToUpload); 
} 

(Re: No perché async/return await su SyncContacts - vedere il punto di Yuval - rendendo il metodo asincrono e in attesa del risultato sarebbe stato wasteful in this instance)

Per un progetto WPF, dovrebbe essere OK per utilizzare Task.Run per eseguire il lavoro collegato alla CPU dal thread chiamante (ma i progetti not so for MVC or WebAPI Asp.Net).

Inoltre, assumendo il lavoro contactsToUpload mappatura è thread-safe, e che la vostra applicazione ha pieno utilizzo delle risorse degli utenti, si potrebbe anche prendere in considerazione parallelizzare la mappatura per ridurre i tempi di esecuzione complessiva:

var contactsToUpload = this.AllContacts 
    .AsParallel() 
    .Select(contact => MapToUploadContact(contact)); 
    // or simpler, .Select(MapToUploadContact); 
Problemi correlati