0
votes

C #, pourquoi simple attendre la tâche.Delay (1); "Active" exécutions parallèles?

Je voudrais poser une question simple sur le code ci-dessous: xxx pré>

Cet exemple exécute les fonctions de calcul de manière synchrone. Quand la ligne "// attend la tâche.delay (1);" Sera valide, les fonctions de calcul seront exécutées de manière parallèle. P>

La question est la suivante: pourquoi, en ajoutant simple attendre, la fonction calc est alors asynchrone? Je sais à propos des exigences de paire Async / Awai. Je demande ce qu'il se passe vraiment lorsque le délai d'attente simple est ajouté au début d'une fonction. La fonction de calcul entière est ensuite reconnue pour être exécutée dans un autre thread, mais pourquoi? P>

EDIT 1: strong> p>

Lorsque j'ai ajouté un fil de lecture au code:

static async Task<int> Calc(int a)
        {
            await Task.Delay(1);
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

            int result = 0;
           
            for (int k = 0; k < a; ++k)
            {
                for (int l = 0; l < a; ++l)
                {
                    result += l;
                }
            }

            return result;
        }


2 commentaires

Ajouter async à une déclaration de méthode ne le rend pas magiquement asynchrone, ni n'utilise de threads séparés. Sans le attendre la tâche.Delay (1); Déclaration, votre méthode est synchrone à 100% et fonctionnera ainsi séquentiellement. Cependant, avec la tâche impliquée, vous impliquez maintenant le pool de threads pour exécuter la continuation (le code après le délai). De plus, lorsque le code dans une méthode async atteint un attendre une tâche lorsque la tâche n'est pas encore terminée, il mettra la file d'attente du reste de la méthode comme une continuation, puis de retourner , permettant à tous ces 3 appels de méthode pour commencer comme ça.


En ce qui concerne la modification, oui, dans une demande de console spécifiquement, les tâches s'exécuteront sur différents threads. Non, ce n'est pas la raison pour laquelle ils commencent tous simultanément. La raison est que avec tâche.delay , l'exécution revient à mainasync avant la fin de la tâche, et sans cela ne le fait pas, comme expliqué dans Cette meilleure version de Ma réponse originale . Même se produirait si les tâches ne fonctionnaient pas sur des threads distincts.


4 Réponses :


1
votes

Une fonction ASYNC renvoie la tâche incomplète à l'appelant à son premier attendre . Après cela, le attendre du côté appelant attendra cette tâche à devenir complète.

sans le attendre la tâche.delay (1) , calc () n'a aucun attente de son propre, de sorte que ne reviendra que à l'appelant quand il s'exécute à la fin. A ce stade, la tâche renvoyée est déjà terminée, le attendre sur le site d'appel utilise immédiatement le résultat sans invoquer la machine ASYNC.


0 commentaires

0
votes

En utilisant un motif asynchronisé / attendre, vous avez l'intention de votre méthode calc pour exécuter en tant que tâche: xxx

Nous devons établir que les tâches sont généralement < EM> asynchrones dans leur nature, mais pas parallèle - le parallélisme est une caractéristique de threads. Cependant, sous la hotte, Certains seront utilisés pour exécuter vos tâches sur. Selon le contexte, vous exécutez votre code dans, plusieurs tâches peuvent être exécutées ou non en parallèle ou de manière séquentielle - mais elles seront (autorisées) asynchrones.

Un bon moyen d'internaliser cela est décrit une enseignant (the thread) répondant à la question (les tâches) en classe. Un enseignant ne sera jamais en mesure de répondre à deux questions différentes simultanément - c'est-à-dire en parallèle - mais elle sera en mesure de répondre aux questions de plusieurs étudiants et peut également être interrompue avec de nouvelles questions entre les deux.


Spécifiquement, async / attendre est une fonctionnalité de multirofesseur coopérative (accent sur la "coopérative") où les tâches ne doivent être programmées que sur un thread si ce fil est libre - et si une tâche est déjà exécutée sur ce fil, il doit céder manuellement la place. . (Que ce soit et combien de threads sont disponibles pour l'exécution dépendent de l'environnement que votre code est exécuté.)

lors de l'exécution Task.Delay (1) Le code annonce qu'il s'agit de dormir, quels signaux à la Planificateur qu'une autre tâche peut exécuter, permettant ainsi une asynchronicité. La façon dont il est utilisé dans votre code, c'est, en substance, une version légèrement pire de Tâche.yield (vous pouvez trouver plus de détails à ce sujet dans les commentaires ci-dessous).

En règle générale, chaque fois que vous Await , la méthode actuellement exécutée est "retournée" de, marquant le code après une continuation. Cette suite ne sera exécutée que lorsque le planificateur de tâche sélectionne à nouveau la tâche actuelle de l'exécution. Cela peut arriver si aucune autre tâche n'exécute actuellement et n'attend que d'être exécutée - par ex. Parce qu'ils ont tous cédé ou attendre quelque chose "longueur longue".

dans votre exemple, les rendements Calc en raison de la tâche en raison de la tâche . Délai et l'exécution retourne à la méthode principale . Cela pénètre à son tour dans la méthode suivante Calc et le modèle se répète. Comme il a été établi dans d'autres réponses, ces continuations peuvent ou non exécuter sur différents threads, en fonction de l'environnement - sans contexte de synchronisation (par exemple dans une application de console), il peut arriver. Pour être clair, ce n'est ni une caractéristique de la tâche .delay ni async / attendre, mais de la configuration de votre code s'exécute dans. Si vous Exigez le parallélisme, utilisez des threads appropriés ou Assurez-vous que vos tâches sont lancées de telle sorte qu'elles encouragent l'utilisation de threads multiples.

dans une autre note: chaque fois que vous souhaitez exécuter un code synchrone de manière asynchrone, utilisez tâche.run () pour l'exécuter. Cela s'assurera que cela ne vous empêche pas trop en utilisant toujours un fil de fond. Cela répond donc sur longRunning Les tâches peuvent être perspicaces.


12 commentaires

Async / attendre n'est pas concerné par les threads et n'a rien à voir avec la planification de la coopération et du fil de la coopérative. S'il vous plaît voir Stackoverflow.com/q/17661428/11683 ou blog.stephancleary.com/2013/11/Ille-is-no-ththread.html .


Chaque tâche est exécutée sur un fil. Ces threads sont gérés par le planificateur de tâches.


Je ne suis pas sûr de savoir pourquoi cela a été marqué ... Je suis plongé ... bien que un peu technique, ce ne soit pas faux.


@Seabizkit Il a été voté parce que c'est très faux. Async / attendre ne concerne pas les threads et dire que c'est très trompeur. L'OP a une application de console qui n'a pas de contexte de synchronisation, de sorte que les tâches n'auraient aucune obligation de fonctionner sur le même thread. Ils peuvent donc très facilement se retrouver sur différents threads, un pour chaque A , < Code> B et C , qui déprime déjà la partie "si ce fil est libre" portion, car il est un fil disponible à chaque fois, mais il n'est pas utilisé .


@Seabizkit Si le même code a été exécuté dans une application WinForms, où le contexte de synchronisation est lié au thread d'interface utilisateur, alors a , b et c Est-ce que tous couriraient sur le même fil, mais ils seraient tous en vol simultanément, pas en ordre de l'achèvement, ce qui l'évite de l'autre côté: il n'est pas un fil disponible, mais tous les trois Les tâches sont démarrées simultanément.


@Gserg Quel que soit le thread est utilisé, il doit être gratuit pour qu'une tâche soit programmée. Qu'il s'agisse ou non de multiples threads disponibles est une question totalement différente, comme ce que le thread est utilisé pour exécuter une continuation. Si cela ne serait pas sur les threads, alors exécutez thread.sleep (int32.maxvalue) dans une tâche serait une chose parfaitement sensée à faire ... et pourtant ce n'est pas le cas.


La tâche .Delay (1) peut être configurée pour capturer le contexte de synchronisation actuel ou non. La tâche .yield n'est pas configurable. Il capture toujours le contexte. C'est une raison pour laquelle ces deux ne sont pas équivalents. L'autre des raisons est que la tâche .yield quand attendu va toujours répondre à IsCompleted avec false . Ceci n'est pas garanti pour Task.Delay (1) , et l'attente de cela pourrait être une opération synchrone (potentiellement).


Je pense que la réponse pourrait être améliorée en mentionnant que le comportement serait différent s'il est exécuté sur un fil de l'interface utilisateur.


Tout ce qui se passe est faux. "Les tâches sont asynchrones dans leur nature" calc n'est pas asynchrone. Il devrait être clairement, mais parce que cela a été mis en œuvre de manière incorrecte, ce n'est pas le cas. "Mais pas parallèle" si c'est étaient asynchrones, ce que l'OP va le courir en parallèle, quels que soient ses internes. "Certains threads seront utilisés pour exécuter vos tâches sur" Non, de nombreuses tâches représentent le travail pas fait par n'importe quel fil du tout . C'est juste (espérons-le) asynchrone, d'une manière ou d'une autre.


"Selon le contexte, vous exécutez votre code dans, plusieurs tâches peuvent être exécutées ou non en parallèle ou de manière séquentielle" uniquement si ces méthodes sont en train de programmer sur le contexte de synchronisation actuel. Ils ne devraient faire que cela pour le travail qui ne fonctionne pas longtemps. "Async / attendre est une caractéristique de multipérapage coopérative (accent sur" Coopérative ") où les tâches ne doivent être programmées que sur un thread". Les tâches sont une représentation des travaux effectués à l'avenir, et non un mécanisme de planification des travaux sur les threads. La planification des travaux sur les threads est distincte et non inhérente aux tâches.


"Lors de l'exécution de la tâche.Delay (1), le code annonce qu'il souhaite dormir" Non, fonctionnant qui produit une tâche qui sera complète dans un milliseconde. Il ne fait rien à l'exécution de la méthode actuelle du tout. "Notez que chaque fois que vous attendez, la méthode actuellement exécutée est" retournée "de" Non, c'est le premier attendre d'une tâche qui n'est pas déjà complète.


"Cette suite ne sera exécutée que lorsque le planificateur de tâches sélectionne la tâche actuelle d'exécution" si vous configurez l'attente pour utiliser le contexte de synchronisation actuel et s'il y en a un, la suite est planifiée sur ce contexte. Ce n'est pas la même chose que ce que vous avez dit.



1
votes

la version de layman ...

Rien dans le processus ne donne que le temps de processeur de processeur sans "retard" et donc cela ne donne rien d'autre temps de la CPU, vous le confondez avec plusieurs Code fileté. "async et attendre" est pas à propos de multiples filetés, mais à propos de l'utilisation de la CPU (fil / threads) lorsque c'est faire NON CPU Work "aka écrire sur le disque. L'écriture sur le disque pas besoin du fil (CPU). Ainsi, quand quelque chose est async, il peut libérer le fil et être utilisé pour autre chose au lieu d'attendre la tâche non de la CPU (OI) à compléter.

@sunside dit le la même chose juste plus techniquement. xxx

vs xxx

la raison pour laquelle il ressemble à sa "manière parallèle" est que Il suffit de donner aux autres tâches des tâches du processeur.

exemple; aka sans délai

  • attendre un; Faites cela (pas de travail AYSNC réel)
  • attendre B; Faites cela (pas de travail AYSNC réel)
  • attendre c; Faites cela (pas de travail AYSNC réel)

    exemple 2; alias avec délai

    • attendre un; Commencez ensuite cette pause ceci (faux async), commencez B, mais revenez et finissez un
    • attendre B; Commencez ceci alors mettre en pause ceci (faux async), start c mais reviens et finir b
    • attendre c; Commencez-le alors mettre en pause ceci (faux async), revenir et finir C

      Ce que vous devriez trouver est bien que davantage démarré plus tôt, l'heure globale sera plus longue que celle-ci de sauter entre tâches sans avantage avec une tâche asynchrone façonnée. où comme, si l'attente de la tâche.delay (1) était une vraie fonction asynchrone aka dans la nature asynchrone, la prestation serait que cela peut démarrer l'autre travail à l'aide du fil qui serait bloqué ... pendant qu'il attend quelque chose qui ne nécessite pas le thread.

      Mise à jour Code idiot pour montrer son mode plus lent ... Assurez-vous que vous êtes en mode "Libération", vous devez toujours ignorer la première exécution ... Ce test sont idiotes et vous auriez besoin d'utiliser https://github.com/dotnet/benchmarkdotnet pour vraiment voir le diff xxx


12 commentaires

L'exemple classique de travail d'E / S fait des demandes Web. L'écriture sur le disque n'est pas réellement pure d'E / S, en raison de la mise en œuvre des API asynchrones connexes (elles sont implémentées Mauvaise ).


@Theodorzoulias à 100% mais l'idée est là .... Fondamentalement quelque chose qui n'est pas limité par le fil actuel. Comme je l'ai dit, j'essayais de tomber en panne .. comme certains marquaient la touche Solean Soled Down ... ce qui n'a aucun sens pour moi. Je pense qu'il y avait une idée de la compréhension. Je comprends ... je ne pose pas la question ... J'essaie simplement de donner un exemple simple et non super spécifique .. pour allonger la fondation ... du point Solefide était allongé ... j'essayais de souligner que Sans Tâche.Delay (1); Vous écrivez simplement une opération synchrone car elle est synchrone dans la nature, qui ...


N'aurait changé que d'avoir plus de sens si vous aviez un exemple d'opération qui soutient l'asynchrone de nature. Aka on pourrait écrire sur le disque ou lire à partir du disque. Il y en a beaucoup mais essayait de se rapporter à celui que PPL peut se rapporter à ..


@Theodorzoulias en.wikipedia.org/wiki/input/output


@Seabizkit En fait, l'heure globale avec des tâches asynchrones trutées (attendre la tâche.delay (1)) est 3 fois plus courte. C'est la raison pour laquelle j'ai utilisé une phrase parallèle, car l'observation du temps, me donner la conclusion que tous les codes après attendre la tâche. Delay, a été réalisée dans différents threads. Je ne sais pas si j'ai raison dans cette conclusion?


@ user1885750 Je viens d'écrire un test ... ce qui le prouve son plus lent .... Donc, je suppose que tout dépend de la manière dont vous écrivez le code que vous utilisez pour tester, si vous testez le code, le test est défectueux. Ill ajouter le code de test i pour vérifier ... Ce n'est pas et une science exacte et vous devez utiliser des outils de marquage de banc réels, mais même dans ce test de brut, vous pouvez voir sa plus lente.


@Seabizkit Mais il y a une énorme différence dans votre code par rapport à mon. Votre code: Tâche a = CALC2 (18000); Attendre un; TÂCHE B = CALC2 (18000); attendre B; TÂCHE C = CALC2 (18000); attendre c; mon code est: tâche a = calc (18000); TÂCHE B = CALC (18000); TÂCHE C = CALC (18000); Attendre un; attendre B; attendre c; J'ai testé mon code à l'aide de STOPWACH (version, application de console, noyau .NET) et il a redonné à ce code avec attendre la tâche.Delay est beaucoup plus rapide.


@ user1885750 Je voyais aussi ce que vous étiez ... mais je sais mieux en termes de tester et de ce que le code fait en réalité, je sais que ce sera plus lent (dans ce cas) ... mais pour aider avec Ceci .. vous devez utiliser Google "Le mode de publication de débogage VS dans .NET pour la performance" qui vous mènera à .. Github .com / dotnet / Benchmarkdototnet .


@Seabizkit Je ne sais pas ce que vous voulez dire exactement. Pensez-vous que j'ai mesuré le code dans le mauvais sens? Ce n'est pas une science de la fusée. Il suffit d'utiliser mon code (pas le vôtre) et de le mesurer. J'ai obtenu 0,5S sans délai et 0,28 avec délai (console, noyau .NET, version). Je ne sais pas pourquoi tu as obtenu les résultats opposés. Peut-être que quelqu'un d'autre testera le code aussi.


Les miens et les urs sont la même chose fondamentalement ... simplement raccourci la longueur de lecture et couru plusieurs fois ... N'utilisez jamais les premiers résultats ... (Ceci s'appelle l'échauffement ou le démarrage à froid) de toute façon que vous vouliez prendre la moyenne. .., vous travaillez avec une telle petite à ce sujet .. que c'est fondamentalement un-mesurable ..Run le même test multiple plusieurs fois. Non ... Exécutez le même code ... Plusieurs fois pas du début froid. En mode de libération ... et vous verrez que chaque fois si plus de 100 appels le seul sans le retard gagnera. La faction de la durée de la CPU que vous essayez de mesurer est très très


ps ce lien est or docs.microsoft.com/en-us/archive/blogs/benwilli/...


@Sabizkit mais c'est évident. Je vous donne le temps moyen de nombreuses mesures. Je mettez en œuvre des algorithmes pendant de nombreuses années et j'ai une expérience sur les mesures de temps. J'ai mesuré le temps d'exécution plusieurs fois dans une période de programme. J'ai également mesuré un autre problème / algorithmes en utilisant le même scénario (avec des tâches d'arborescence et en attente de retard). De plus, j'ai mesuré ce problème en dehors de Visual Studio et j'ai toujours des résultats similaires. Peut-être que c'est à cause de la demande de console.



4
votes

Il est important de comprendre comment async code> méthodes fonctionne.

Premièrement, ils commencent à fonctionner de manière synchrone, sur le même thread, tout comme chaque méthode. Sans que attendre la tâche.Delay (1) code> ligne, le compilateur vous avera averti que la méthode serait complètement synchrone. Le mot clé code> async code> ne fait fonctionner que votre méthode asynchrone. Il suffit d'utiliser attendre code>. P>

La magie se produit au premier attendre code> qui agit sur une tâche incomplète code>. À ce stade, la méthode retourne em>. Il renvoie une tâche code> que vous pouvez utiliser pour vérifier lorsque le reste de la méthode est terminé. P>

donc lorsque vous avez attendre la tâche.delay (1) code > là, la méthode revient à cette ligne, permettant à votre MAINSTYNC code> de passer à la ligne suivante et de démarrer l'appel suivant sur Calc code>. p>

Comment La poursuite de calc code> exécute (tout après attendre la tâche.delay (1) code>) dépend du cas de "contexte de synchronisation". Dans ASP.NET (NON COE) ou une application d'interface utilisateur, par exemple, le contexte de synchronisation contrôle la manière dont les continuations fonctionnent et qu'ils enlèveraient l'une après l'autre. Dans une application UI, ce serait sur le même fil que cela a commencé. Dans ASP.NET, il peut s'agir d'un fil différent, mais toujours l'un après l'autre. Donc, dans les deux cas, vous ne verriez aucun parallélisme. P>

Toutefois, car il s'agit d'une application de console, qui n'a pas de contexte de synchronisation, les continuations se produisent sur tout thread threadpool dès que le Tâche code> à partir de Task.Delay (1) code> est terminé. Cela signifie que chaque continuation peut se produire en parallèle. P>

Vaut également la peine de noter: commençant par C # 7.1, vous pouvez faire votre MAIN / code> Méthode code> Async code>, éliminant le besoin de Votre MAINSTOYNC CODE> Méthode: P>

static async Task Main(string[] args)


0 commentaires