Question

J'apprends encore le async/await, alors excusez-moi si je demande quelque chose d'évident.Prenons l'exemple suivant :

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;
            }
        );
    }

}

Cela se comporte comme prévu et imprime "33" dans la console.

Si je remplace le deuxième await avec une attente explicite...

static async Task<int> FooAsync() {

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

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

    return result1 + result2;

}

... Il me semble avoir le même comportement.

Ces deux exemples sont-ils complètement équivalents ?

Et s'ils sont équivalents dans ce cas, existe-t-il d'autres cas où le remplacement du dernier await par une attente explicite ferait une différence ?

Était-ce utile?

La solution 3

OK, je pense avoir compris cela, alors laissez-moi le résumer, dans ce qui sera, je l'espère, une explication plus complète que les réponses fournies jusqu'à présent...

Réponse courte

Remplacement du deuxième await avec une attente explicite n'aura aucun effet appréciable sur une application console, mais bloquera le thread d'interface utilisateur d'une application WPF ou WinForms pendant la durée de l'attente.

De plus, la gestion des exceptions est légèrement différente (comme l'a noté Stephen Cleary).

Longue réponse

En un mot, le await est ce que ca:

  1. Si la tâche attendue est déjà terminée, elle récupère simplement son résultat et continue.
  2. Si ce n'est pas le cas, des postes la suite (le reste de la méthode après le await) au actuel contexte de synchronisation, s'il en existe un.Essentiellement, await essaie de nous ramener là d’où nous sommes partis.
    • S'il n'y a pas de contexte actuel, il utilise simplement le contexte d'origine Planificateur de tâches, qui est généralement un pool de threads.

Le deuxième (et le troisième et ainsi de suite...) await fait de même.

Étant donné que les applications console n'ont généralement pas de contexte de synchronisation, les continuations seront généralement gérées par le pool de threads, il n'y a donc aucun problème si nous bloquons dans la continuation.

WinForms ou WPF, en revanche, ont un contexte de synchronisation implémenté au-dessus de leur boucle de messages.Donc, await exécuté sur un thread d'interface utilisateur exécutera (éventuellement) également sa suite sur le thread d'interface utilisateur.Si nous bloquons dans la suite, cela bloquera la boucle de message et rendra l'interface utilisateur insensible jusqu'à ce que nous débloquions.OTOH, si seulement nous await, il publiera proprement les continuations qui seront éventuellement exécutées sur le thread d'interface utilisateur, sans jamais bloquer le thread d'interface utilisateur.

Dans le formulaire WinForms suivant, contenant un bouton et une étiquette, en utilisant await maintient l'interface utilisateur réactive à tout moment (notez le async devant le gestionnaire de clics ):

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;
            }
        );
    }

}

Si on remplaçait le deuxième await dans FooAsync avec t2.Result, il continuerait à répondre pendant environ 3 secondes après le clic sur le bouton, puis se figerait pendant environ 2 secondes :

  • La suite après le premier await attendra poliment son tour pour être programmé sur le fil de discussion de l'interface utilisateur, ce qui se produirait après Method1Async() la tâche se termine, c'est-à-direaprès environ 3 secondes,
  • à quel point le t2.Result bloquera grossièrement le fil de l'interface utilisateur jusqu'à ce que le Method2Async() la tâche se termine, environ 2 secondes plus tard.

Si nous supprimions le async en face de la button1_Click et remplacé son await avec FooAsync().Result ce serait une impasse :

  • Le fil de discussion de l'interface utilisateur attendrait FooAsync() tâche à terminer,
  • qui attendrait que sa suite se termine,
  • qui attendrait que le fil de discussion de l'interface utilisateur soit disponible,
  • ce qui n'est pas le cas, puisqu'il est bloqué par le FooAsync().Result.

L'article "Attendre, SynchronizationContext et applications de console" de Stephen Toub m'a été d'une aide précieuse pour comprendre ce sujet.

Autres conseils

Votre version de remplacement bloque le thread appelant en attendant la fin de la tâche.Il est difficile de voir une différence visible dans une application console comme celle-là puisque vous bloquez intentionnellement dans Main, mais elles ne sont certainement pas équivalentes.

Ils ne sont pas équivalents.

Task.Result bloque jusqu’à ce que le résultat soit disponible.Comme je l'explique sur mon blog, ceci peut provoquer des blocages si tu as un async contexte qui nécessite un accès exclusif (par exemple, une interface utilisateur ou une application ASP.NET).

Aussi, Task.Result enveloppera toutes les exceptions dans AggregateException, la gestion des erreurs est donc plus difficile si vous bloquez de manière synchrone.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top