web-dev-qa-db-fra.com

Plusieurs attend dans une seule méthode

J'ai une méthode comme celle-ci:

public static async Task SaveAllAsync()
{
    foreach (var kvp in configurationFileMap)
    {
        using (XmlWriter xmlWriter = XmlWriter.Create(kvp.Value, XML_WRITER_SETTINGS))
        {
            FieldInfo[] allPublicFields = 
                           kvp.Key.GetFields(BindingFlags.Public | BindingFlags.Static);
            await xmlWriter.WriteStartDocumentAsync();
            foreach (FieldInfo fi in allPublicFields)
            {
                await xmlWriter.WriteStartElementAsync("some", "text", "here");
            }
            await xmlWriter.WriteEndDocumentAsync();
        }
    }
}

Mais j'ai du mal à suivre ce qui se passera lorsque quelqu'un appellera SaveAllAsync()

Ce que je pense qui va arriver est la suivante:

  1. Quand quelqu'un l'appelle pour la première fois, SaveAllAsync() rendra le contrôle à l'appelant à la ligne await xmlWriter.WriteStartDocumentAsync();
  2. Ensuite ... Quand ils attendent SaveAllAsync() (ou attendent la tâche) ... Que se passe-t-il? SaveAllAsync() sera-t-il toujours bloqué sur la première attente jusqu'à ce que cela soit appelé? Parce qu'il n'y a pas de filetage impliqué, je suppose que c'est le cas ...
36
Xenoprimate

Vous pouvez penser à await comme "suspendre" la méthode async jusqu'à la fin de l'opération. Dans des cas particuliers, si l'opération est déjà terminée (ou extrêmement rapide), la variable await ne "mettra pas en pause" la méthode; il continuera à exécuter immédiatement.

Donc, dans ce cas (en supposant que WriteStartDocumentAsync n'est pas déjà terminé), await mettra la méthode en pause et renverra une tâche non terminée à l'appelant. Notez que la Task renvoyée par une méthode async représente cette méthode; lorsque la méthode est terminée, alors cette Task est terminée.

Finalement, WriteStartDocumentAsync sera terminé et le programme de la méthode async continuera à être exécuté. Dans ce cas, la prochaine partie de la méthode sera exécutée jusqu'à la prochaine await, quand elle sera à nouveau suspendue, etc. Finalement, la méthode async sera complétée, ce qui complétera la Task renvoyée pour représenter cette méthode.

Pour plus d'informations, j'ai une intro async/await sur mon blog .

41
Stephen Cleary

La réponse de Stephens est bien sûr correcte. Voici une autre façon de penser qui pourrait aider.

Le continuation d'un morceau de code correspond à ce qui se passe une fois le code terminé. Lorsque vous frappez une await, deux choses se produisent. Premièrement, la position actuelle dans l'exécution devient la suite de la tâche attendue. Deuxièmement, le contrôle laisse la méthode actuelle et certains autres codes sont exécutés. L'autre code est peut-être la suite du premier appel, ou peut-être quelque chose de complètement différent, un gestionnaire d'événements, par exemple.

Mais lorsque l'appel à xmlWriter.WriteStartDocumentAsync() est terminé; ce qui se produit? L'exécution en cours est-elle interrompue et renvoyée à SaveAllAsync()

Ce que vous entendez par "achèvement" n’est pas clair. WriteStartDocumentAsync démarre une écriture asynchrone, probablement sur un thread d'achèvement d'E/S, et vous renvoie une Task qui représente ce travail asynchrone. Attendre cette tâche fait deux choses, comme je l'ai dit. Premièrement, la suite de cette tâche devient la position actuelle du code. Deuxièmement, le contrôle laisse la méthode actuelle et certains autres codes sont exécutés. Dans ce cas, le code appelé SaveAllAsync exécute la suite de cet appel.

Supposons maintenant que le code - l'appelant de SaveAllAsync continue de s'exécuter et supposons davantage que vous soyez dans une application avec un thread d'interface utilisateur, comme une application Windows Forms ou une application WPF. Nous avons maintenant deux threads: le thread d'interface utilisateur et un thread d'achèvement IO. Le thread d'interface utilisateur exécute l'appelant de SaveAllAsync, qui est renvoyé, et le thread d'interface utilisateur est maintenant assis dans une boucle gérant des messages Windows pour déclencher des gestionnaires d'événements. 

Finalement, IO se termine et le thread d'achèvement IO envoie une note au thread d'interface utilisateur indiquant "vous pouvez exécuter la poursuite de cette tâche maintenant". Si le thread d'interface utilisateur est occupé, ce message est mis en file d'attente; le thread d'interface finit par y arriver et invoque la suite. Le contrôle reprend après la première await et vous entrez dans la boucle.

WriteStartElementAsync est maintenant appelé. Il lance à nouveau un code qui dépend de quelque chose qui se passe sur le thread d’achèvement IO (vraisemblablement; son travail est à la hauteur, mais c’est une supposition raisonnable), qui renvoie une Task représentant ce travail, et le fil d'interface utilisateur attend cette tâche. De nouveau, la position actuelle dans l'exécution est inscrite en tant que continuation de cette tâche et le contrôle retourne à l'appelant qui a appelé la première continuation, à savoir le processeur d'événements du thread d'interface utilisateur. Il continue joyeusement le traitement des messages jusqu'au jour où le fil IO le signale et dit que le travail que vous avez demandé est effectué sur le fil d’achèvement IO, veuillez invoquer la poursuite de cette tâche. faire le tour encore une fois ...

Avoir un sens? 

22
Eric Lippert

Chaque fois qu'une fonction est "asynchrone", cela signifie que lorsqu'une "attente" est effectuée sur une System.Threading.Tasks.Task, il se produit deux choses principales:

  1. La position actuelle dans l'exécution devient une "continuation" de la tâche attendue, ce qui signifie qu'une fois la tâche terminée, elle fera le nécessaire pour s'assurer que le reste de la méthode asynchrone est appelé. Ce travail peut être effectué dans un thread spécifique, dans un thread de pool de threads aléatoire ou peut-être avec le thread d'interface utilisateur. Cela dépend du SynchronizationContext que la tâche obtient pour sa "continuation".

  2. Le contrôle est retourné à:

    • S'il s'agit de la première attente dans la méthode async, il retourne à la méthode d'appel d'origine avec un System.Threading.Tasks.Task qui représente la méthode async (PAS les tâches créées dans la méthode async). Il peut simplement l'ignorer et continuer, l'attendre avec un Task.Result/Task.Wait () (attention de ne pas bloquer le thread d'interface utilisateur) ou même faire une attente sur celui-ci s'il s'agit d'une méthode asynchrone.

    • S'il ne s'agit pas de la première attente dans la méthode asynchrone, il sera simplement renvoyé à n'importe quel gestionnaire du thread exécutant la "continuation" de la dernière tâche attendue.

Donc pour répondre:

Quand ils attendent SaveAllAsync () (ou attendent la tâche) ... Que se passe-t-il?

Faire attendre SaveAllAsync () ne causera pas NÉCESSAIREMENT au blocage de l’un de ses attend internes. Cela est dû au fait qu'une attente sur SaveAllAsync () revient simplement à l'appelant de la méthode appelée SaveAllAsync () qui peut continuer comme si rien ne s'était passé, tout comme la tâche interne de SaveAllAsync () l'attendait. Cela garde le thread en mouvement et en mesure de répondre (éventuellement) à la demande ultérieurement afin d'exécuter la "continuation" du premier attente interne de SaveAllAsync (): wait xmlWriter.WriteStartDocumentAsync () '. Ainsi, peu à peu, SaveAllAsync () se terminera et rien ne restera bloqué.

MAIS ... si vous OR un autre code plus profond effectue toujours un Task.Result/Task.Wait () sur l'une des Tâches renvoyées par une attente, cela risque de bloquer certains éléments si la "continuation" tente pour s'exécuter sur le même thread que le code en attente.

0
simon hearn