Domanda

Sto ancora imparando il async / await, quindi per favore scusami se sto chiedendo qualcosa di ovvio.Considera il seguente esempio:

class Program  {

    static void Main(string[] args) {
        var result = FooAsync().Result;
        Console.WriteLine(result);
    }

    static async Task<int> FooAsync() {

        var t1 = Method1Async();
        var t2 = Method2Async();

        var result1 = await t1;
        var result2 = await t2;

        return result1 + result2;

    }

    static Task<int> Method1Async() {
        return Task.Run(
            () => {
                Thread.Sleep(1000);
                return 11;
            }
        );
    }

    static Task<int> Method2Async() {
        return Task.Run(
            () => {
                Thread.Sleep(1000);
                return 22;
            }
        );
    }

}
.

Questo si comporta come previsto e stampa "33" nella console.

Se sostituisco il secondo await con un attesa esplicito ...

static async Task<int> FooAsync() {

    var t1 = Method1Async();
    var t2 = Method2Async();

    var result1 = await t1;
    var result2 = t2.Result;

    return result1 + result2;

}
.

... Sembra avere lo stesso comportamento.

Questi due esempi sono completamente equivalenti?

E se sono equivalenti in questo caso, ci sono altri casi in cui la sostituzione dell'ultimo await da un'aspetto esplicito farebbe una differenza?

È stato utile?

Soluzione 3

Ok, penso di averlo capito così, quindi lascia che lo si spera, in ciò che si spera sia una spiegazione più completa delle risposte fornite finora ...

risposta breve

Sostituzione del secondo await con un'attesa esplicita non avrà alcun effetto apprezzabile su un'applicazione console, ma bloccherà la filettatura UI di un'applicazione WPF o WinForms per la durata dell'attesa.

Inoltre, la gestione delle eccezioni è leggermente diversa (come notato da Stephen Cleary).

risposta lunga

In breve, il await fa questo:

    .
  1. Se l'attività attesa è già finita, recupera il suo risultato e continua.
  2. Se non lo è, post La continuazione (il resto del metodo dopo il await) al corrente contesto di sincronizzazione, se ce n'è uno. Essenzialmente, await sta cercando di restituzione di noi dove abbiamo iniziato da.
      .
    • Se non c'è un contesto corrente, utilizza semplicemente l'originale Taskscheduler , che è solitamente il pool di filo.

      il secondo (e terzi e così via ...) await fa lo stesso.

      Poiché le applicazioni della console non hanno in genere alcun contesto di sincronizzazione, le continuazioni verranno normalmente gestite dal pool di filo, quindi non vi è alcun problema se blocciamo entro la continuazione.

      Winforms o WPF, d'altra parte, avere un contesto di sincronizzazione implementato sulla parte superiore del loro ciclo di messaggi. Pertanto, await eseguita su un thread UI (alla fine) eseguirà anche la sua continuazione sulla filettatura dell'interfaccia utente. Se ci succediamo a bloccare la continuazione, bloccherà il cambio del messaggio e renderà l'interfaccia utente non reattiva finché non sblocciamo. OTOH, se solo await, pubblicherà ordinatamente le continuazioni da eseguire alla fine eseguita sulla filettatura dell'interfaccia utente, senza mai bloccare il filo UI.

      Nella seguente forma di Winforms, contenente un pulsante e un'etichetta, utilizzando await mantiene l'interfaccia utente reattivo in qualsiasi momento (nota il async davanti al gestore clickler):

      public partial class Form1 : Form {
      
          public Form1() {
              InitializeComponent();
          }
      
          private async void button1_Click(object sender, EventArgs e) {
              var result = await FooAsync();
              label1.Text = result.ToString();
          }
      
          static async Task<int> FooAsync() {
      
              var t1 = Method1Async();
              var t2 = Method2Async();
      
              var result1 = await t1;
              var result2 = await t2;
      
              return result1 + result2;
      
          }
      
          static Task<int> Method1Async() {
              return Task.Run(
                  () => {
                      Thread.Sleep(3000);
                      return 11;
                  }
              );
          }
      
          static Task<int> Method2Async() {
              return Task.Run(
                  () => {
                      Thread.Sleep(5000);
                      return 22;
                  }
              );
          }
      
      }
      
      .

      Se abbiamo sostituito il secondo await in FooAsync con t2.Result, continuerebbe a essere reattivi per circa 3 secondi dopo il pulsante clicca, quindi bloccare per circa 2 secondi:

        .
      • Il proseguimento dopo il primo await attenderà educatamente il suo turno di essere programmato sulla filettatura dell'interfaccia utente, che avverrà dopo che la commissione Method1Async() finisce, I.e. Dopo circa 3 secondi,
      • A quale punto il t2.Result bloccherà brudamente la filettatura dell'interfaccia utente fino a quando l'attività Method2Async() finisce, circa 2 secondi dopo.

        Se abbiamo rimosso il async davanti al button1_Click e sostituito il suo await con FooAsync().Result IT STAFLOCK:

          .
        • Il thread dell'interfaccia utente attenderebbe l'attività FooAsync() per terminare,
        • che aspetterebbe la sua continuazione alla finitura,
        • che aspetterebbe il filo dell'interfaccia utente per diventare disponibile,
        • che non è, poiché è bloccato dal FooAsync().Result.

          L'articolo "Attendi, sincronizzazioneContext e app console " di Stephen Toub è stato inestimabile per capire questo argomento.

Altri suggerimenti

La versione sostitutiva blocca il thread chiamante in attesa dell'attività di finire.È difficile vedere una differenza visibile in un'app console come quella dal momento che stai bloccando intenzionalmente nel principale, ma non sono decisamente equivalenti.

non sono equivalenti.

Blocchi Task.Result fino a quando il risultato è disponibile.Dato che spiego sul mio blog, questo può causare deadlock Se si dispone di un contesto async che richiede accesso esclusivo (ad esempio, un'app UI o ASP.NET).

Inoltre, Task.Result avvolgerà tutte le eccezioni in AggregateException, quindi la gestione degli errori è più difficile se si è sincronizzata.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top