IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C# Discussion :

Observation sur les Tasks, l'asynchrone..


Sujet :

C#

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre expérimenté
    Homme Profil pro
    Administrateur Systèmes, Clouds et Réseaux /CAO/DAO/Ingénierie Electrotechnique
    Inscrit en
    Décembre 2014
    Messages
    458
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Administrateur Systèmes, Clouds et Réseaux /CAO/DAO/Ingénierie Electrotechnique

    Informations forums :
    Inscription : Décembre 2014
    Messages : 458
    Par défaut Observation sur les Tasks, l'asynchrone..
    Hello tout le monde,

    Aimant beaucoup m'intéresser au parallélisme et asynchronisme, j'ai lu et relu le sujet sur le site qui parle des pools de thread et qui est fort intéressant.

    par pur intérêt à un moment, après avoir bossé avec sur mon programme et observé des réactions assez étranges en mesurant les temps d'éxécution... J'ai à un moment poussé le vice à demander un travail parallèle sur tous les fichiers contenus dans un programme, avec un travail simple.. et au lieu de me faire baisser le temps de travail, celui ci augmentait de temps en temps. Sur ces temps d'éxécutions quand j'ai simulé ce travail sur l'équivalent d'un fort nombre de fichiers sur une dizaine de dossier (qui eux mêmes bossaient sur différentes tâches) , j'ai observé ponctuellement que ça montait, j'ai mis en place un mode de calcul de temps moyen... et c'était moins intéressant.. je me suis donc mis de coté un tout petit algorithme encore plus simple et c'est là que j'ai percuté que le nombre de tâches était limité sur mon proc à 9 (si j'ai bien lu) en parallèle. (Il ne me semble pas que c'était abordé dans le tutoriel), après quoi c'est temporairement bloquant si j'ai bien fait attention, avant de relancer un autre paquet... Ce qui fait qu'au final les mesures en temps sont assez impressionnantes.
    J'ai testé sous plusieurs formes donc le parallel.foreach (qui amène pas grand chose si ce n'est que ça évite d'écrire une méthode), et finalement j'ai tenté le mix, dont je ne comprenais pas l'intérêt en fait au prime abord... entre task.run et async.

    et bref je voulais en faire profiter si certains ne s'étaient jamais posé la question puis aussi avoir confirmation, peut être que j'ai loupé un truc.

    Donc premiere version (attention faut avoir ~27s à perdre - du moins sur mon proc , alors que ça devrait être 5s)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
     
    namespace ConsoleApp2
    {
        class Program
        {
            static string Foo()
            {
                for (int i = 1; i < 6; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine(i);
     
                    // await Task.Delay(1000);
                }
                Console.WriteLine("délais terminé");
                return "Marsupial";
            }
     
              static void Main(string[] args)
            {
                Stopwatch sw2 = new Stopwatch();
                sw2.Start();
     
                List<string> mammouth = new List<string>();
     
                // tâche normale
                Task[] arrTask = new Task[100];
     
                for (int i = 0; i < 100; i++)
                {
                    //  tâche normale
                    var res = Task.Run(() => mammouth.Add(Foo()));
     
                    arrTask[i] = res;
                }
     
                Console.WriteLine("Fini");
     
                Task.WaitAll(arrTask);
                foreach (string test in mammouth)
                {
                    Console.WriteLine(test);
                }
                Console.WriteLine(sw2.ElapsedMilliseconds);
                Console.Read();
     
     
            }
        }
    }
    Et donc une version mixant qui est proche des 5s... 5 272ms:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
     
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
     
    namespace ConsoleApp2
    {
        class Program
        {
            static string Foo()
            {
                for (int i = 0; i < 5; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine(i);
     
                    // await Task.Delay(1000);
                }
                Console.WriteLine("délais terminé");
                return "Marsupial";
            }
     
            static async Task<string> AsyncFoo()
            {
                for (int i = 1; i < 6; i++)
                {
                    await Task.Delay(1000);
                    Console.WriteLine(i);
     
                    // await Task.Delay(1000);
                }
                Console.WriteLine("délais terminé");
                return "Marsupial";
            }
     
     
            static void Main(string[] args)
            {
                Stopwatch sw2 = new Stopwatch();
                sw2.Start();
     
                List<string> mammouth = new List<string>();
     
                // tâche normale
                Task[] arrTask = new Task[100];
     
                for (int i = 0; i < 100; i++)
                {
                    //  tâche normale
                    var res = Task.Run(async () => mammouth.Add(await AsyncFoo()));
     
                    arrTask[i] = res;
                }
     
                Console.WriteLine("Fini");
     
                Task.WaitAll(arrTask);
                foreach (string test in mammouth)
                {
                    Console.WriteLine(test);
                }
                Console.WriteLine(sw2.ElapsedMilliseconds);
                Console.Read();
     
     
            }
        }
    }

    Bon faites pas gaffe aux noms, c'est quand je n'ai pas envie de me casser la tête à en chercher un.

    Je trouve l'exemple assez frappant (pas moi), et personnellement ça m'a vraiment fait réfléchir car avant je voyais pas trop l'intérêt, du moins dans le cadre de ce que je faisais, du "pourquoi mixer" ....

    Bien sur sous réserve que je n'ai pas loupé quelque chose, le thread.sleep par exemple comparé au task.delay, ça agit très différemment.

  2. #2
    Expert éminent Avatar de Pol63
    Homme Profil pro
    .NET / SQL SERVER
    Inscrit en
    Avril 2007
    Messages
    14 204
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : .NET / SQL SERVER

    Informations forums :
    Inscription : Avril 2007
    Messages : 14 204
    Par défaut
    pas très clair

    le parallélisme c'est découper quelque chose en plusieurs morceaux, pouvant être exécutés par plusieurs cœurs de processeur via plusieurs threads
    par défaut la plupart des choses qui font ca dans le Framework (parallel.foreach, threadpool etc...) se limitent au nombre de cœur de ta machine, car souvent créer plus de threads n'apportera rien, voir ralentira (contraintes techniques du changement de cache nécessaire à un changement de thread sur un cœur)
    typiquement une liste de x items où pour chacun tu veux faire des calculs qui peuvent être fait dans n'importe quel ordre

    par contre pour certaines choses découper en plusieurs threads n'apporte pas toujours un gain de performance, par exemple si tu as 50 fichiers à lire sur un vieux disque dur (pas ssd), lancer 50 threads pour lire tous les fichiers en même temps va ralentir énormément (contrainte technique lié au déplacement de la tête de lecture pour satisfaire tout le monde en lisant un petit bout de chacun des fichiers chacun leur tour)


    l'asynchronisme c'est quand quelque chose prend du temps, pour pouvoir le lancer sur un autre thread (ou pas) et attendre que ca soit fini sans utiliser le thread principal (ou pas)
    je dis "ou pas" car async/await triche un peu là dessus dans certains cas mais ca revient au même
    et donc là on parle d'un accès à une base de données, à un webservice, un socket… , où techniquement on envoie une requête et on ne fait rien en attendant la réponse

    edit : relu et je comprends mieux mais je suis pas expert en task, et j'ai pas le temps tout de suite de tester ton code
    Cours complets, tutos et autres FAQ ici : C# - VB.NET

  3. #3
    Membre chevronné
    Avatar de nouanda
    Homme Profil pro
    Hobbyist
    Inscrit en
    Mai 2002
    Messages
    246
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Australie

    Informations professionnelles :
    Activité : Hobbyist

    Informations forums :
    Inscription : Mai 2002
    Messages : 246
    Par défaut
    Citation Envoyé par Pol63 Voir le message
    edit : relu et je comprends mieux mais je suis pas expert en task, et j'ai pas le temps tout de suite de tester ton code
    Il va falloir attendre le passage de François Dorin

  4. #4
    Expert éminent Avatar de Pol63
    Homme Profil pro
    .NET / SQL SERVER
    Inscrit en
    Avril 2007
    Messages
    14 204
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : .NET / SQL SERVER

    Informations forums :
    Inscription : Avril 2007
    Messages : 14 204
    Par défaut
    oui mais je le vois pas trop en ce moment ...
    d'habitude j'ai juste à dire qu'on peut démarrer des centaines de threads pour qu'il vienne nuancer mes propos ^^
    enfin faudra bien que je me penche sérieusement sur ces tasks et ce qui se passe derrière ...
    Cours complets, tutos et autres FAQ ici : C# - VB.NET

  5. #5
    Membre émérite
    Homme Profil pro
    Développeur / architecte
    Inscrit en
    Juillet 2009
    Messages
    473
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Développeur / architecte

    Informations forums :
    Inscription : Juillet 2009
    Messages : 473
    Par défaut
    Je ne comprends pas trop la question. Si celle-ci est pourquoi le 1er exemple prends plus de temps que le second, c'est parce que ça tient à la différence de simulation du temps d'attente:
    • Thread.Sleep() fait attendre de manière active le thread courant (il ne peut rien faire d'autre)
    • Task.Delay() comme tout ce qui est asynchrone permet de mettre le thread courant en suspension

    Dans le premier cas, les threads de ton pool passent leur temps à attendre (et du coup le fx doit allouer de plus en plus de threads dans le pool je suppose), dans le second cas ils peuvent faire autre chose et ils "ContinueWith()" le travail entamé précédemment dans l'automate caché/défini par le await…

    Je pense que si tu testes le nombre de thread dispo du pool dans tes 2 programmes ça sera sans doute très différent.

  6. #6
    Expert confirmé

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 761
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 761
    Billets dans le blog
    21
    Par défaut
    Citation Envoyé par chrisdot Voir le message
    Je ne comprends pas trop la question. Si celle-ci est pourquoi le 1er exemple prends plus de temps que le second, c'est parce que ça tient à la différence de simulation du temps d'attente:
    • Thread.Sleep() fait attendre de manière active le thread courant (il ne peut rien faire d'autre)
    • Task.Delay() comme tout ce qui est asynchrone permet de mettre le thread courant en suspension

    Dans le premier cas, les threads de ton pool passent leur temps à attendre (et du coup le fx doit allouer de plus en plus de threads dans le pool je suppose), dans le second cas ils peuvent faire autre chose et ils "ContinueWith()" le travail entamé précédemment dans l'automate caché/défini par le await…
    C'est incorrect, même si l'idée est là. Thread.Sleep suspend le thread courant (pas de manière active). Il est bloqué pendant le délai spécifié et ne peut rien faire d'autre.
    Task.Delay suspend la tâche courante (et non le thread). Le thread exécutant la tâche est alors replacé sur le pool de threads, disponible pour effectuer d'autres opérations.



    Pour en revenir à la question initiale, c'est un peu fourbe.

    Bon, le plus simple, c'est de commencer par le cas utilisant async/await. Dans cette version, ce sont les tâches qui sont suspendues. Aussi, dès qu'une tâche est suspendue, elle libère le thread sur lequel elle s'exécutait, thread qui peut donc exécuter une autre tâche. Ainsi, cette version du programme peut lancer les 100 tâches et les traités très rapidement.

    Pour la seconde version, sans async/await, il faut savoir que le pool de thread dispose d'un ordonnanceur, et nous en avons ici un "effet de bord" qui est visible. Lorsqu'on fait appel à Task.Run, la tâche est mise dans une liste d'attente. Cette liste est ensuite traitée au fur et à mesure par l'ordonnanceur, qui peut ou non créer de nouveau thread pour exécuter les tâches. L'ordonnanceur tente généralement de limiter le nombre de threads utilisés. Autrement dit, il ne crée pas, pour chaque tâche dans la file d'attente, un nouveau thread. Il peut juger plus opportun de retarder le lancement d'un nouveau thread pour pouvoir réutiliser un thread qui va se libérer d'ici quelques millisecondes.

    Seulement voilà, ici, les tâches ne durent pas quelques millisecondes, mais plusieurs secondes, et l'effet est donc largement visible. Par contre, si on aide un peu l'ordonnanceur et qu'on lui dit que les tâches sont des tâches longues, alors là le comportement sera celui attendu.

    Manque de bol, impossible de préciser via Task.Run que les tâches sont des tâches longues. Il faut revenir au bon vieux Task.Factory.StartNew pour cela.

    Donc, il suffit de remplacer
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    var res = Task.Run(() => mammouth.Add(Foo(numTache)));
    par
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    var res = Task.Factory.StartNew(() => mammouth.Add(Foo(numTache)), TaskCreationOptions.LongRunning);
    et tout rentrera dans l'ordre. En effet, l'implémentation actuelle de l'ordonnanceur, voyant des tâches longues arriver, leur dédie un thread. S'il y en a un de disponible sur le pool de thread, il l'utilise, sinon, il en crée un (au lieu d'attendre un peu pour voir si l'un d'eux se libère).

    Au passage, il y a un petit risque d'accès concurrent à la variable mammouth. Pour un programme d'exemple, ce n'est pas bien grave, mais dans un vrai programme, il faudrait le protéger par un mutex.

  7. #7
    Modérateur
    Avatar de DotNetMatt
    Homme Profil pro
    CTO
    Inscrit en
    Février 2010
    Messages
    3 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : CTO
    Secteur : Finance

    Informations forums :
    Inscription : Février 2010
    Messages : 3 611
    Billets dans le blog
    3
    Par défaut
    Citation Envoyé par nouanda Voir le message
    Il va falloir attendre le passage de François Dorin
    Il est en vacances jusqu'a la mi-Juillet
    Less Is More
    Pensez à utiliser les boutons , et les balises code
    Desole pour l'absence d'accents, clavier US oblige
    Celui qui pense qu'un professionnel coute cher n'a aucune idee de ce que peut lui couter un incompetent.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 0
    Dernier message: 12/12/2014, 20h16
  2. Réponses: 5
    Dernier message: 11/03/2013, 15h05
  3. confirmation message sur les tasks
    Par gmoder dans le forum W4 Express
    Réponses: 1
    Dernier message: 09/07/2012, 10h31
  4. Réponses: 0
    Dernier message: 05/01/2009, 18h54

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo