Question Comment et quand utiliser `async` et` await`


De ma compréhension l'une des principales choses que async et await faire est de rendre le code facile à écrire et à lire - mais les utilise-t-il comme des threads de fond générateurs pour exécuter une logique de longue durée?

J'essaie actuellement l'exemple le plus basique. J'ai ajouté quelques commentaires en ligne. Pouvez-vous le préciser pour moi?

// I don't understand why this method must be marked as `async`.
private async void button1_Click(object sender, EventArgs e)
{
    Task<int> access = DoSomethingAsync();
    // task independent stuff here

    // this line is reached after the 5 seconds sleep from 
    // DoSomethingAsync() method. Shouldn't it be reached immediately? 
    int a = 1; 

    // from my understanding the waiting should be done here.
    int x = await access; 
}

async Task<int> DoSomethingAsync()
{
    // is this executed on a background thread?
    System.Threading.Thread.Sleep(5000);
    return 1;
}

726
2018-01-22 09:29


origine


Réponses:


En utilisant async et await le compilateur génère une machine d'état en arrière-plan.

Voici un exemple sur lequel j'espère pouvoir expliquer certains détails de haut niveau:

public async Task MyMethodAsync()
{
    Task<int> longRunningTask = LongRunningOperationAsync();
    // independent work which doesn't need the result of LongRunningOperationAsync can be done here

    //and now we call await on the task 
    int result = await longRunningTask;
    //use the result 
    Console.WriteLine(result);
}

public async Task<int> LongRunningOperationAsync() // assume we return an int from this long running operation 
{
    await Task.Delay(1000); // 1 second delay
    return 1;
}

OK, alors qu'est-ce qui se passe ici:

  1. Task<int> longRunningTask = LongRunningOperationAsync(); commence à s'exécuter LongRunningOperation

  2. Le travail indépendant est fait en supposant que le thread principal (Thread ID = 1) puis await longRunningTask est atteint.

    Maintenant, si le longRunningTask n'a pas fini et il fonctionne toujours, MyMethodAsync() retournera à sa méthode d'appel, ainsi le thread principal ne sera pas bloqué. Quand le longRunningTask est fait alors un thread du ThreadPool (peut être n'importe quel thread) reviendra à MyMethodAsync() dans son contexte précédent et continuer l'exécution (dans ce cas, imprimer le résultat à la console).

Un deuxième cas serait que le longRunningTask a déjà terminé son exécution et le résultat est disponible. Lorsque vous atteignez le await longRunningTask nous avons déjà le résultat donc le code continuera à s'exécuter sur le même thread. (dans ce cas, imprimer le résultat sur la console). Bien sûr, ce n'est pas le cas pour l'exemple ci-dessus, où il y a un Task.Delay(1000) impliqué.


546
2017-11-14 18:55



Suite aux autres réponses, jetez un oeil à await (C # Référence)

et plus précisément à l'exemple inclus, cela explique un peu votre situation

L'exemple Windows Forms suivant illustre l'utilisation de await dans un   méthode asynchrone, WaitAsynchronouslyAsync. Contraster le comportement de cette   méthode avec le comportement de WaitSynchronously. Sans attendre   opérateur appliqué à une tâche, WaitSynchronously s'exécute de manière synchrone   malgré l'utilisation du modificateur asynchrone dans sa définition et un appel à   Thread.Sleep dans son corps.

private async void button1_Click(object sender, EventArgs e)
{
    // Call the method that runs asynchronously.
    string result = await WaitAsynchronouslyAsync();

    // Call the method that runs synchronously.
    //string result = await WaitSynchronously ();

    // Display the result.
    textBox1.Text += result;
}

// The following method runs asynchronously. The UI thread is not
// blocked during the delay. You can move or resize the Form1 window 
// while Task.Delay is running.
public async Task<string> WaitAsynchronouslyAsync()
{
    await Task.Delay(10000);
    return "Finished";
}

// The following method runs synchronously, despite the use of async.
// You cannot move or resize the Form1 window while Thread.Sleep
// is running because the UI thread is blocked.
public async Task<string> WaitSynchronously()
{
    // Add a using directive for System.Threading.
    Thread.Sleep(10000);
    return "Finished";
}

126
2018-01-22 09:39



D'après ce que je comprends, l'une des principales choses que async et attend est de rendre le code facile à écrire et à lire.

Ils doivent faire asynchrone code facile à écrire et à lire, oui.

Est-ce la même chose que générer des threads d'arrière-plan pour exécuter une logique de longue durée?

Pas du tout.

// Je ne comprends pas pourquoi cette méthode doit être marquée comme "asynchrone".

le async mot-clé permet la await mot-clé. Donc, toute méthode utilisant await doit être marqué async.

// cette ligne est atteinte après les 5 secondes de sommeil de la méthode DoSomethingAsync (). Ne devrait-il pas être atteint immédiatement?

Non parce que async Les méthodes ne sont pas exécutées sur un autre thread par défaut.

// est-ce exécuté sur un thread d'arrière-plan?

Non.


Vous pouvez trouver mon async/await intro utile. le documents MSDN officiels sont également inhabituellement bons (en particulier le ROBINET section), et async équipe a mis un excellent FAQ.


105
2018-01-22 13:17



Explication

Voici un exemple rapide d'async / await à un niveau élevé. Il y a beaucoup plus de détails à considérer au-delà de cela.

Remarque: Task.Delay(1000) simule faire du travail pendant 1 seconde. Je pense qu'il est préférable de considérer cela comme une réponse d'une ressource externe. Puisque notre code attend une réponse, le système peut mettre la tâche en cours de côté et y revenir une fois l'opération terminée. En attendant, il peut faire un autre travail sur ce fil.

Dans l'exemple ci-dessous, le premier bloc fait exactement cela. Il commence toutes les tâches immédiatement (le Task.Delay lignes) et les met sur le côté. Le code fera une pause sur le await a ligne jusqu'à ce que le délai de 1 seconde est fait avant d'aller à la ligne suivante. Depuis b, c, d, et e tous ont commencé à s'exécuter presque au même moment que a (en raison de l'absence de l'attente), ils devraient terminer à peu près au même moment dans ce cas.

Dans l'exemple ci-dessous, le deuxième bloc commence une tâche et attend qu'elle se termine (c'est ce que await fait) avant de commencer les tâches suivantes. Chaque itération de ceci prend 1 seconde. le await met le programme en pause et attend le résultat avant de continuer. C'est la principale différence entre le premier et le second bloc.

Exemple

Console.WriteLine(DateTime.Now);

// This block takes 1 second to run because all
// 5 tasks are running simultaneously
{
    var a = Task.Delay(1000);
    var b = Task.Delay(1000);
    var c = Task.Delay(1000);
    var d = Task.Delay(1000);
    var e = Task.Delay(1000);

    await a;
    await b;
    await c;
    await d;
    await e;
}

Console.WriteLine(DateTime.Now);

// This block takes 5 seconds to run because each "await"
// pauses the program until the task finishes
{
    await Task.Delay(1000);
    await Task.Delay(1000);
    await Task.Delay(1000);
    await Task.Delay(1000);
    await Task.Delay(1000);
}
Console.WriteLine(DateTime.Now);

SORTIE:

5/24/2017 2:22:50 PM
5/24/2017 2:22:51 PM (First block took 1 second)
5/24/2017 2:22:56 PM (Second block took 5 seconds)

Informations supplémentaires sur SynchronizationContext

Note: C'est là que les choses deviennent un peu brumeuses pour moi, donc si je me trompe, corrigez-moi et je mettrai à jour la réponse. Il est important d'avoir une compréhension de base de la façon dont cela fonctionne, mais vous pouvez vous en sortir sans être un expert à ce sujet tant que vous n'utilisez jamais ConfigureAwait(false)Même si vous risquez de perdre certaines occasions d'optimisation, je présume.

Il y a un aspect de ceci qui rend le concept Async / Wait un peu plus difficile à saisir. C'est le fait que dans cet exemple, tout se passe sur le même thread (ou du moins ce qui semble être le même thread en ce qui concerne son SynchronizationContext). Par défaut, await va restaurer le contexte de synchronisation du thread d'origine sur lequel il s'exécutait. Par exemple, dans ASP.NET, vous avez un HttpContext lié à un thread lorsqu'une requête arrive. Ce contexte contient des éléments spécifiques à la requête Http d'origine, tels que l'objet Request d'origine, qui contient des informations telles que la langue, l'adresse IP, les en-têtes, etc. Si vous passez d'un thread à mi-chemin dans le traitement de quelque chose, vous pourriez potentiellement essayer d'extraire des informations de cet objet sur un HttpContext différent qui pourrait être désastreux. Si vous savez que vous n'utiliserez pas le contexte pour quelque chose, vous pouvez choisir de ne pas s'en soucier. Cela permet essentiellement à votre code de s'exécuter sur un thread séparé sans apporter le contexte avec lui.

Comment faites-vous cela? Par défaut, le await a; code fait en fait une supposition que vous voulez capturer et restaurer le contexte:

await a; //Same as the line below
await a.ConfigureAwait(true);

Si vous souhaitez autoriser le code principal à continuer sur un nouveau thread sans le contexte d'origine, utilisez simplement false au lieu de true pour qu'il sache qu'il n'a pas besoin de restaurer le contexte.

await a.ConfigureAwait(false);

Une fois le programme mis en pause, il continuera potentiellement sur un fil entièrement différent avec un contexte différent. C'est de là que viendrait l'amélioration des performances - il pourrait continuer sur n'importe quel thread disponible sans avoir à restaurer le contexte d'origine avec lequel il a démarré.

Est-ce que ce truc est déroutant? Enfer ouais! Pouvez-vous le comprendre? Probablement! Une fois que vous avez une compréhension des concepts, passez aux explications de Stephen Cleary qui ont tendance à être plus orientées vers quelqu'un avec une compréhension technique de l'async / attente déjà.


82
2018-05-26 14:55



Montrer les explications ci-dessus en action dans un simple programme de console -

class Program
{
    static void Main(string[] args)
    {
        TestAsyncAwaitMethods();
        Console.WriteLine("Press any key to exit...");
        Console.ReadLine();
    }

    public async static void TestAsyncAwaitMethods()
    {
        await LongRunningMethod();
    }

    public static async Task<int> LongRunningMethod()
    {
        Console.WriteLine("Starting Long Running method...");
        await Task.Delay(5000);
        Console.WriteLine("End Long Running method...");
        return 1;
    }
}

Et la sortie est:

Starting Long Running method...
Press any key to exit...
End Long Running method...

Ainsi,

  1. Main démarre la méthode longue via TestAsyncAwaitMethods. Cela revient immédiatement sans interrompre le fil en cours et nous voyons immédiatement le message 'Appuyez sur n'importe quelle touche pour quitter'
  2. Pendant ce temps, le LongRunningMethod fonctionne en arrière-plan. Une fois terminé, un autre thread de Threadpool détecte ce contexte et affiche le message final

Ainsi, pas de thread est bloqué.


44
2017-08-13 09:32



Je pense que vous avez choisi un mauvais exemple avec System.Threading.Thread.Sleep

Point d'un async La tâche est de le laisser s'exécuter en arrière-plan sans verrouiller le thread principal, comme faire un DownloadFileAsync

System.Threading.Thread.Sleep n'est pas quelque chose qui est "en train de se faire", il dort juste, et donc votre prochaine ligne est atteinte après 5 secondes ...

Lisez cet article, je pense que c'est une excellente explication de async et await concept: http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx


33
2018-01-22 09:32



Voici un programme de console rapide pour le rendre clair à ceux qui suivent. La méthode "TaskToDo" est votre méthode à long terme que vous voulez faire asynchrone. Le faire exécuter Async est fait par la méthode TestAsync. La méthode des boucles de test exécute simplement les tâches "TaskToDo" et les exécute Async. Vous pouvez voir cela dans les résultats parce qu'ils ne se terminent pas dans le même ordre d'exécution à exécution - ils rapportent au thread d'interface utilisateur de la console quand ils se terminent. Simpliste, mais je pense que les exemples simplistes mettent mieux en évidence le noyau du pattern que les exemples plus impliqués:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestingAsync
{
    class Program
    {
        static void Main(string[] args)
        {
            TestLoops();
            Console.Read();
        }

        private static async void TestLoops()
        {
            for (int i = 0; i < 100; i++)
            {
                await TestAsync(i);
            }
        }

        private static Task TestAsync(int i)
        {
            return Task.Run(() => TaskToDo(i));
        }

        private async static void TaskToDo(int i)
        {
            await Task.Delay(10);
            Console.WriteLine(i);
        }
    }
}

16
2017-07-17 15:36



Cette réponse vise à fournir des informations spécifiques à ASP.NET.

En utilisant async / await dans le contrôleur MVC, il est possible d'augmenter l'utilisation du pool de threads et d'atteindre un débit bien meilleur, comme expliqué dans l'article ci-dessous,

http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4

Dans les applications Web qui voient un grand nombre de demandes simultanées à   démarrage ou a une charge en rafale (où la simultanéité augmente soudainement),   rendre ces appels de service Web asynchrones augmentera la   la réactivité de votre application. Une requête asynchrone prend le   même temps de traitement qu'une demande synchrone. Par exemple,   si une demande fait un appel de service Web qui nécessite deux secondes pour   terminée, la requête prend deux secondes si elle est effectuée   de manière synchrone ou asynchrone. Cependant, lors d'un appel asynchrone,   un thread n'est pas bloqué de répondre à d'autres demandes alors qu'il   attend la fin de la première demande. Par conséquent, asynchrone   demandes empêchent la file d'attente des demandes et la croissance du pool de threads lorsqu'il y a   de nombreuses requêtes simultanées qui appellent des opérations longues.


10
2017-09-08 02:06



Pour être honnête, je pense toujours que la meilleure explication est celle sur l'avenir et les promesses sur Wikipédia: http://en.wikipedia.org/wiki/Futures_and_promises

L'idée de base est que vous disposez d'un pool distinct de threads qui exécutent les tâches de manière asynchrone. En l'utilisant. L'objet fait cependant la promesse qu'il exécutera l'opération à un moment donné et vous donnera le résultat quand vous le demanderez. Cela signifie qu'il sera bloqué lorsque vous demanderez le résultat et qu'il n'a pas fini, mais que vous l'exécuterez dans le pool de threads.

De là, vous pouvez optimiser les choses: certaines opérations peuvent être implémentées de manière asynchrone et vous pouvez optimiser des choses comme les E / S de fichiers et la communication réseau en regroupant les demandes suivantes et / ou en les réorganisant. Je ne suis pas sûr si cela est déjà dans le cadre de travail de Microsoft - mais si ce n'est pas ce serait l'une des premières choses que je voudrais ajouter.

Vous pouvez réellement implémenter le futur tri des modèles avec des rendements en C # 4.0. Si vous voulez savoir comment cela fonctionne exactement, je peux vous recommander ce lien qui fait un travail décent: http://code.google.com/p/fracture/source/browse/trunk/Squared/TaskLib/ . Cependant, si vous commencez à en jouer vous-même, vous remarquerez que vous avez vraiment besoin de support linguistique si vous voulez faire toutes les choses sympas - ce qui est exactement ce que Microsoft a fait.


9
2018-01-22 10:16



Toutes les réponses ici utilisent Task.Delay () ou une autre fonction asynchrone intégrée. Mais voici mon exemple qui n'utilise aucune de ces fonctions asynchrones:

    // Starts counting to a large numbewr and then immediately displays message "i'm counting...". 
    // Then it waits for task to finish and displays "finished, press any key".
    static void asyncTest ()
    {
        Console.WriteLine("Started asyncTest()");
        Task<long> task = asyncTest_count();
        Console.WriteLine("Started counting, please wait...");
        task.Wait(); // if you comment this line you will see that message "Finished counting" will be displayed before we actually finished counting.
        //Console.WriteLine("Finished counting to " + task.Result.ToString()); // using task.Result seems to also call task.Wait().
        Console.WriteLine("Finished counting.");
        Console.WriteLine("Press any key to exit program.");
        Console.ReadLine();
    }

    static async Task<long> asyncTest_count()
    {
        long k = 0;
        Console.WriteLine("Started asyncTest_count()");
        await Task.Run(() =>
        {
            long countTo = 100000000;
            int prevPercentDone = -1;
            for (long i = 0; i <= countTo; i++)
            {
                int percentDone = (int)(100 * (i / (double)countTo));
                if (percentDone != prevPercentDone)
                {
                    prevPercentDone = percentDone;
                    Console.Write(percentDone.ToString() + "% ");
                }

                k = i;
            }
        });
        Console.WriteLine("");
        Console.WriteLine("Finished asyncTest_count()");
        return k;
    }

8
2017-07-15 08:03



Voir ce violon https://dotnetfiddle.net/VhZdLU (et l'améliorer si possible) pour exécuter un application console simple qui montre les usages de Tâche, Task.WaitAll (), asynchrone et attente opérateurs dans le même programme.

Ce violon devrait effacer votre concept de cycle d'exécution.

Voici l exemple de code

using System;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {               
        var a = MyMethodAsync(); //Task started for Execution and immediately goes to Line 19 of the code. Cursor will come back as soon as await operator is met       
        Console.WriteLine("Cursor Moved to Next Line Without Waiting for MyMethodAsync() completion");
        Console.WriteLine("Now Waiting for Task to be Finished");       
        Task.WaitAll(a); //Now Waiting      
        Console.WriteLine("Exiting CommandLine");       
    }

    public static async Task MyMethodAsync()
    {
        Task<int> longRunningTask = LongRunningOperation();
        // independent work which doesn't need the result of LongRunningOperationAsync can be done here
        Console.WriteLine("Independent Works of now executes in MyMethodAsync()");
        //and now we call await on the task 
        int result = await longRunningTask;
        //use the result 
        Console.WriteLine("Result of LongRunningOperation() is " + result);
    }

    public static async Task<int> LongRunningOperation() // assume we return an int from this long running operation 
    {
        Console.WriteLine("LongRunningOperation() Started");
        await Task.Delay(2000); // 2 second delay
        Console.WriteLine("LongRunningOperation() Finished after 2 Seconds");
        return 1;
    }   

}

Trace provenant de la fenêtre de sortie: enter image description here


6
2018-01-24 12:45