2015-01-29 11 views
8

Ho seguito this question e ho compreso le ragioni alla base della risposta popolare (anche se non ancora accettata) da Peter Duniho. Specificamente, sono consapevole che non attesa di una successiva operazione di esecuzione prolungata bloccherà il thread UI:Dovrebbero essere attese operazioni annidate attendibili?

Il secondo esempio non cedere durante l'operazione asincrona. Invece, ottenendo il valore della proprietà content.Result, si forza il thread corrente ad attendere fino al completamento dell'operazione asincrona.

Ho anche confermato questo, per il mio vantaggio, in questo modo:

private async void button1_Click(object sender, EventArgs e) 
{ 
    var value1 = await Task.Run(async() => 
     { 
      await Task.Delay(5000); 
      return "Hello"; 
     }); 

    //NOTE: this one is not awaited... 
    var value2 = Task.Run(async() => 
     { 
      await Task.Delay(5000); 
      return value1.Substring(0, 3); 
     }); 

    System.Diagnostics.Debug.Print(value2.Result); //thus, UI freezes here after 5000 ms. 
} 

Ma ora mi chiedo: cosa è necessario await tutte le operazioni "awaitable" annidati all'interno di un awaitable più esterno operazione? Ad esempio, posso fare questo:

private async void button1_Click(object sender, EventArgs e) 
{ 
    var value0 = await Task.Run(() => 
     { 
      var value1 = new Func<Task<string>>(async() => 
      { 
       await Task.Delay(5000); 
       return "hello"; 
      }).Invoke(); 

      var value2 = new Func<string, Task<string>>(async (string x) => 
       { 
        await Task.Delay(5000); 
        return x.Substring(0, 3); 
       }).Invoke(value1.Result); 

      return value2; 
     }); 

    System.Diagnostics.Debug.Print(value0); 
} 

O posso fare questo:

private async void button1_Click(object sender, EventArgs e) 
{ 
    //This time the lambda is async... 
    var value0 = await Task.Run(async() => 
     { 
      //we're awaiting here now... 
      var value1 = await new Func<Task<string>>(async() => 
      { 
       await Task.Delay(5000); 
       return "hello"; 
      }).Invoke(); 

      //and we're awaiting here now, too... 
      var value2 = await new Func<string, Task<string>>(async (string x) => 
       { 
        await Task.Delay(5000); 
        return x.Substring(0, 3); 
       }).Invoke(value1); 

      return value2; 
     }); 

    System.Diagnostics.Debug.Print(value0); 
} 

e nessuno dei due congelare la UI. Quale è preferibile?

+0

Sarei molto sorpreso se quelli tutti Stampa la stessa cosa, credo che si sta stampando un oggetto Task piuttosto che il suo risultato. –

+0

@BenVoigt - No, bc in entrambi i casi l'attività viene scartata dal momento in cui è attesa. –

+0

Ma uno di questi è il wrapping di una seconda attività. –

risposta

13

L'ultimo è preferibile (sebbene piuttosto disordinato)

In TAP (Task basata Motivo Asincrono) Un task (e altri awaitables) rappresentano un'operazione asincrona. Hai fondamentalmente 3 opzioni di gestione di questi compiti:

  • aspettare sincrono (DoAsync().Result, DoAsync().Wait()) - Blocca il thread chiamante fino a quando l'operazione è completata. Rende la tua applicazione più dispendiosa, meno scalabile, meno reattiva e suscettibile di deadlock.
  • Attendere in modo asincrono (await DoAsync()) - Non blocca il thread chiamante. In pratica registra il lavoro dopo lo await come continuazione da eseguire dopo il completamento dell'attività atteso.
  • Non attendere (DoAsync()) - Non blocca il thread chiamante, ma non attende il completamento dell'operazione. Lei è a conoscenza di eventuali eccezioni generate durante DoAsync viene elaborato

In particolare, sono consapevole che non è in attesa di una successiva operazione di lunga durata bloccherà il thread UI

Quindi, non del tutto. Se non si attende nulla, nulla bloccherà ma non si può sapere quando o se l'operazione è stata completata con successo. Tuttavia, se si attende in modo sincrono si blocca il thread chiamante e si potrebbero avere deadlock se si sta bloccando il thread dell'interfaccia utente.

Conclusione: Si consiglia di await le vostre attese finché ciò è possibile (ad esempio, non è in Main). Ciò include "operazioni nidificate async-await".

Informazioni sull'esempio specifico: Task.Run viene utilizzato per scaricare lavoro associato alla CPU a un thread ThreadPool, che non sembra essere quello che si sta tentando di imitare.Se usiamo Task.Delay a rappresentare un'operazione veramente asincrona (di solito di I/O-bound) siamo in grado di avere "annidato async-await" senza Task.Run:

private async void button1_Click(object sender, EventArgs e) 
{ 
    var response = await SendAsync(); 
    Debug.WriteLine(response); 
} 

async Task<Response> SendAsync() 
{ 
    await SendRequestAsync(new Request()); 
    var response = await RecieveResponseAsync(); 
    return response; 
} 

async Task SendRequestAsync(Request request) 
{ 
    await Task.Delay(1000); // actual I/O operation 
} 

async Task<Response> RecieveResponseAsync() 
{ 
    await Task.Delay(1000); // actual I/O operation 
    return null; 
} 

È possibile utilizzare delegati anonimi, invece di metodi, ma è a disagio quando è necessario per definire i tipi e invocarli da soli.

Se si ha bisogno di scaricare questa operazione a un filo ThreadPool, basta aggiungere Task.Run:

private async void button1_Click(object sender, EventArgs e) 
{ 
    var response = await Task.Run(() => SendAsync()); 
    Debug.WriteLine(response); 
} 
+0

In realtà sto rileggendo la tua risposta, e mi chiedo se potresti per favore chiarire: stai dicendo che io * non dovrei * usare nested awaits (che è ciò che fa il mio terzo blocco di codice - quello dopo "O posso farlo")? –

+0

@roryap Sto dicendo che se le operazioni sono 'asincrone' allora 'aspettano' loro invece di bloccarsi, non importa dove si trovino. Informazioni sull'uso di 'Task.Run' zero, una o due volte dipende da cosa si sta * effettivamente * cercando di fare (sto assumendo' Task.Delay' qui è solo una sostituzione). – i3arnon

+1

@roryap Se si dispone di una lunga operazione intensiva della CPU che si desidera scaricare, utilizzare 'Task.Run', altrimenti semplicemente richiamare le operazioni' async' e 'attendere '. – i3arnon

Problemi correlati