Qual è la differenza tra Task.Start / Wait e Async / Await?


207

Forse mi manca qualcosa, ma qual è la differenza tra fare:

public void MyMethod()
{
  Task t = Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();
  UpdateLabelToSayItsComplete();
}

public async void MyMethod()
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  UpdateLabelToSayItsComplete();
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}

Risposte:


395

Potrei mancare qualcosa

Sei.

qual'è la differenza tra doing Task.Waite await task?

Ordina il tuo pranzo dal cameriere al ristorante. Un momento dopo aver dato il tuo ordine, un amico entra e si siede accanto a te e inizia una conversazione. Ora hai due scelte. Puoi ignorare il tuo amico fino al completamento dell'attività: puoi aspettare fino all'arrivo della tua zuppa e fare nient'altro mentre aspetti. Oppure puoi rispondere al tuo amico e quando il tuo amico smette di parlare, il cameriere ti porterà la tua zuppa.

Task.Waitsi blocca fino al completamento dell'attività: ignori il tuo amico fino al completamento dell'attività. awaitcontinua a elaborare i messaggi nella coda dei messaggi e, quando l'attività è completa, accoda un messaggio che dice "riprendi da dove eri rimasto dopo l'attesa". Parli con il tuo amico e quando c'è un'interruzione nella conversazione arriva la zuppa.


5
@ronag No, non lo è. Ti piacerebbe se aspettare un tempo Taskche impiegasse 10 ms eseguisse effettivamente una durata di 10 ore Tasksul tuo thread, bloccandoti così per le 10 ore intere?
svick

62
@StrugglingCoder: l' operatore attende non fa altro che valutare il suo operando e quindi restituire immediatamente un'attività al chiamante corrente . Le persone hanno questa idea nella testa che l'asincronia può essere raggiunta solo scaricando il lavoro sui thread, ma questo è falso. Puoi cucinare la colazione e leggere il giornale mentre il toast è nel tostapane senza assumere un cuoco per guardare il tostapane. La gente lo dice bene, ci deve essere un filo - un lavoratore - nascosto all'interno del tostapane, ma ti assicuro che se guardi nel tuo tostapane, non c'è nessun ragazzino che guarda il toast.
Eric Lippert,

11
@StrugglingCoder: Allora, chi sta facendo il lavoro che chiedi? Forse un altro thread sta facendo il lavoro e quel thread è stato assegnato a una CPU, quindi il lavoro è in realtà svolto. Forse il lavoro viene svolto dall'hardware e non esiste alcun thread. Ma sicuramente, dici, ci deve essere qualche thread nell'hardware . No. L'hardware esiste al di sotto del livello dei thread. Non è necessario alcun thread! Potresti trarre vantaggio dalla lettura dell'articolo di Stephen Cleary Non c'è discussione.
Eric Lippert,

6
@StrugglingCoder: Ora, domanda, supponi che ci sia un lavoro asincrono e che non ci sia hardware e che non ci sia altro thread. Com'è possibile? Bene, supponiamo che la cosa che aspettavi mette in coda una serie di messaggi di Windows , ognuno dei quali fa un po 'di lavoro? Ora che succede? Ritorni il controllo sul ciclo dei messaggi, inizia a estrarre i messaggi dalla coda, facendo un po 'di lavoro ogni volta e l'ultimo lavoro che viene fatto è "esegui la continuazione dell'attività". Nessun thread aggiuntivo!
Eric Lippert,

8
@StrugglingCoder: ora, pensa a quello che ho appena detto. Sai già che è così che funziona Windows . Esegui una serie di movimenti del mouse, clic sui pulsanti e quant'altro. I messaggi vengono messi in coda, elaborati a turno, ogni messaggio provoca una piccola quantità di lavoro e, una volta terminato, il sistema continua a funzionare. L'asincronizzazione su un thread non è altro che ciò a cui sei già abituato: suddividere grandi compiti in piccoli bit, metterli in coda ed eseguire tutti i piccoli bit in un certo ordine. Alcune di queste esecuzioni causano la messa in coda di altri lavori e la vita continua. Un filo!
Eric Lippert,

121

Per dimostrare la risposta di Eric ecco un codice:

public void ButtonClick(object sender, EventArgs e)
{
  Task t = new Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();  
  //If you press Button2 now you won't see anything in the console 
  //until this task is complete and then the label will be updated!
  UpdateLabelToSayItsComplete();
}

public async void ButtonClick(object sender, EventArgs e)
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  //If you press Button2 now you will see stuff in the console and 
  //when the long method returns it will update the label!
  UpdateLabelToSayItsComplete();
}

public void Button_2_Click(object sender, EventArgs e)
{
  Console.WriteLine("Button 2 Clicked");
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}

27
+1 per il codice (è meglio eseguire una volta piuttosto che leggere cento volte). Ma la frase " //If you press Button2 now you won't see anything in the console until this task is complete and then the label will be updated!" è fuorviante. Quando si preme il pulsante con il t.Wait();gestore eventi click in pulsante, ButtonClick()non è possibile premere nulla e quindi vedere qualcosa nella console e aggiornare l'etichetta "fino a quando questa attività non è completa" poiché la GUI è bloccata e non risponde, ovvero eventuali clic o interazioni con la GUI sono PERSO fino al completamento dell'attività in attesa
Gennady Vanin Геннадий Ванин

2
Immagino che Eric presuma che tu abbia una conoscenza di base dell'API Task. Guardo quel codice e dico a me stesso "yup t.Waitbloccherà il thread principale fino a quando l'attività non sarà completata."
The Muffin Man

50

Questo esempio dimostra la differenza molto chiaramente. Con async / wait il thread chiamante non si bloccherà e continuerà l'esecuzione.

static void Main(string[] args)
{
    WriteOutput("Program Begin");
    // DoAsTask();
    DoAsAsync();
    WriteOutput("Program End");
    Console.ReadLine();
}

static void DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    t.Wait();
    WriteOutput("3 - Task completed with result: " + t.Result);
}

static async Task DoAsAsync()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    var result = await t;
    WriteOutput("3 - Task completed with result: " + result);
}

static int DoSomethingThatTakesTime()
{
    WriteOutput("A - Started something");
    Thread.Sleep(1000);
    WriteOutput("B - Completed something");
    return 123;
}

static void WriteOutput(string message)
{
    Console.WriteLine("[{0}] {1}", Thread.CurrentThread.ManagedThreadId, message);
}

Uscita DoAsTask:

[1] Inizio del programma
[1] 1 - Avvio
[1] 2 - Attività avviata
[3] A - Ha iniziato qualcosa
[3] B - Completato qualcosa
[1] 3 - Compito completato con il risultato: 123
[1] Fine del programma

Uscita DoAsAsync:

[1] Inizio del programma
[1] 1 - Avvio
[1] 2 - Attività avviata
[3] A - Ha iniziato qualcosa
[1] Fine del programma
[3] B - Completato qualcosa
[3] 3 - Compito completato con il risultato: 123

Aggiornamento: esempio migliorato mostrando l'ID del thread nell'output.


4
Ma se lo faccio: new Task (DoAsTask) .Start (); invece di DoAsAsync (); ottengo la stessa funzionalità, quindi dov'è il vantaggio di
wait

1
Con la tua proposta, il risultato dell'attività deve essere valutato altrove, forse un altro metodo o un lambda. Async-waitit rende il codice asincrono più facile da seguire. È solo un potenziatore della sintassi.
Mas

@Mas non capisco perché la fine del programma sia dopo A - Avviato qualcosa. Dalla mia comprensione quando si tratta di attendere il processo di parole chiave dovrebbe andare immediatamente al contesto principale e poi tornare indietro.

@JimmyJimm Dalla mia comprensione Task.Factory.StartNew farà girare un nuovo thread per eseguire DoSomethingThatTakesTime. In quanto tale, non vi è alcuna garanzia che il Programma Fine o A - Avviato qualcosa vengano eseguiti per primi.
RiaanDP

@JimmyJimm: ho aggiornato l'esempio per mostrare gli ID thread. Come puoi vedere, "Fine programma" e "A - Avviato qualcosa" sono in esecuzione su thread diversi. Quindi in realtà l'ordine non è deterministico.
Mas

10

Wait (), farà eseguire codice potenzialmente asincrono in modo sincronizzato. aspettare non lo farà.

Ad esempio, hai un'applicazione web asp.net. L'utenteA chiama / getUser / 1 endpoint. Il pool di app asp.net sceglierà un thread dal pool di thread (Thread1) e, questo thread effettuerà una chiamata http. Se si esegue Wait (), questo thread verrà bloccato fino alla risoluzione della chiamata http. Durante l'attesa, se UserB chiama / getUser / 2, il pool di app dovrà servire un altro thread (Thread2) per effettuare nuovamente la chiamata http. Hai appena creato (Beh, effettivamente recuperato dal pool di app) un altro thread senza motivo, perché non puoi utilizzare Thread1, è stato bloccato da Wait ().

Se si utilizza wait su Thread1, quindi, SyncContext gestirà la sincronizzazione tra Thread1 e la chiamata http. Semplicemente, avviserà al termine della chiamata http. Nel frattempo, se UserB chiama / getUser / 2, allora utilizzerai di nuovo Thread1 per effettuare una chiamata http, perché è stato rilasciato una volta che l'attesa è stata eseguita. Quindi un'altra richiesta può usarlo, ancora di più. Una volta effettuata la chiamata http (utente1 o utente2), Thread1 può ottenere il risultato e tornare al chiamante (client). Thread1 è stato utilizzato per più attività.


9

In questo esempio, non molto, praticamente. Se stai aspettando un'attività che ritorni su un thread diverso (come una chiamata WCF) o rinunci al controllo sul sistema operativo (come File IO), waitit utilizzerà meno risorse di sistema non bloccando un thread.


3

Nell'esempio sopra, è possibile utilizzare "TaskCreationOptions.HideScheduler" e modificare notevolmente il metodo "DoAsTask". Il metodo in sé non è asincrono, come accade con "DoAsAsync" perché restituisce un valore "Task" ed è contrassegnato come "asincrono", creando diverse combinazioni, ecco come mi dà esattamente lo stesso che usare "async / wait" :

static Task DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime, TaskCreationOptions.HideScheduler); //<-- HideScheduler do the magic

    TaskCompletionSource<int> tsc = new TaskCompletionSource<int>();
    t.ContinueWith(tsk => tsc.TrySetResult(tsk.Result)); //<-- Set the result to the created Task

    WriteOutput("2 - Task started");

    tsc.Task.ContinueWith(tsk => WriteOutput("3 - Task completed with result: " + tsk.Result)); //<-- Complete the Task
    return tsc.Task;
}
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.