[C#] Comment gérer beaucoup (100+) de threads réseaux ?

a marqué ce sujet comme résolu.
Auteur du sujet

Bonjour,

J’ai une fonction assez simple qui récupère le titre de chaque page d’un site via des fichiers textes.

 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
private void feed_Click(object sender, RoutedEventArgs e)
        {
            string[] urls = File.ReadAllLines("pages.txt");
            int c = urls.Length;
            string liste = null;

            for (int i = 0; i < c; ++i)
            {
                try{
                    Uri uri = new Uri(urls[i]);

                    HttpWebRequest http = (HttpWebRequest)WebRequest.Create(uri.Scheme + "://" + uri.Host);
                    WebResponse response = http.GetResponse();

                    StreamReader sr = new StreamReader(response.GetResponseStream());
                    string html = sr.ReadToEnd();

                    MatchCollection matches = osef,etc.

[...]
                }
                catch { }
            }
            File.WriteAllText("resultats.txt", liste);
        }

Le gros problème est que la liste de pages dans pages.txt est assez importante (> 2000) donc toute l’opération prend un temps non négligeable.

Je pensais utiliser les threads mais je suis loin d’être un expert dans ce domaine.

J’imaginais un truc un peu comme ça :

1
2
3
4
5
6
7
Thread th1 = new Thread(get_titres);
th1.Start();

Thread th2 = new Thread(get_titres);
th2.Start();

[...]

Deux problèmes :

1. Il va y avoir beaucoup de threads.

Si je veux utiliser 100 threads, va-t-il falloir créer manuellement un nouvel objet th[N] jusqu’à 100 (th1 … th98, th99, th100) ?

Ou existe-t-il une méthode plus simple ?

2. Comment gérer la boucle for ?

Dans mon exemple mono-thread je me sers du "compteur" i pour marquer le numéro de ligne du fichier pages.txt.

Or ça va être assez difficile à faire avec les threads, non ?

Faut-il faire passer ce i en argument d’une nouvelle fonction ou bien y a-t-il une manière plus simple de procéder ?

Merci de vos réponses, je débute un peu avec les threads.

Édité par SEWERFan

+0 -0

Salut,

Je ne connais pas vraiment le C#, mais je te conseille d’utiliser une queue avec des threads qui font tes opérations.

En gros, tu ajoutes les URLs dans une queue (FIFO) et tu as un nombre n de workers qui "mangent" les taches. Ils récupèrent les URLs dans la queue et exécute ce que tu veux. Pour fermer tout ça, tu ajoutes n valeurs précises (genre null) dans ta queue quand tu as fini d’ajouter toutes tes URLs et quand un worker reçoit cette valeur, il sort de sa boucle.

Tu peux même ajouter la valeur extraite de la requête dans une autre queue avec un ou plusieurs autres threads qui eux "mangent" de cette queue pour sauvegarder les données (en append si c’est dans des fichiers).

Édité par tleb

Je vais détailler un peu plus la réponse du dessus, même si elle est juste :D.

Quand on optimise une partie de code, il faut toujours faire attention à :

  • ce qu’on vise, quels cas d’utilisations dans quel but métier ? Rapide pour l’affichage d’une application mobile, c’est 60 fps (60 frames/secondes), c’est clairement pas envisageable pour une application de communication avec une soude sur la lune. Ou 30 minutes serait rapide. C’est quoi pour toi un temps acceptable ?
  • le matériel, selon le processeur (le nombre de coeur que la machine à, …) on ne vas pas créé le même nombre de thread. Difficile donc de t’aider.

Un processeur ne peut gérer en simultanée qu’une dizaine de thread max pour un i7. Si tu créé 100 threads, tu as perdu le temps d’initialisation de 90 thread, car seulement 10 theads peuvent bosser en même temps sur un i7. La création de thread étant non null, c’est de la perte de ressource inutile. C’est pour cela que le message du-dessus te conseil de créé un pool de thread. L’idée est simple pouvoir ré-utiliser les mêmes thread, pour éviter cette initialisation inutile.

Dans mon exemple mono-thread je me sers du "compteur" i pour marquer le numéro de ligne du fichier pages.txt.

Tu as eu une bonne intuition, effectivement tu ne peux pas juste déclarer i comme une variable de classe. Si le thread un, lit le contenu (exemple i=1), thread 2 s’execute met à jours la variable (i=2), thread 1 fini son traitement et met à jours le contenu de ta variable (i=2). Tu aura des incohérences. Tu peux "locker" avec le mot clé lock et obliger le programme à s’exécuter pendant une série d’instructions sur un seul thread. Attention à ne pas abuser car tu perdrait l’intérêt des threads !

Édité par Hugo

Développeur d’application Android - Clé PGP

+2 -0

Si tu ne connais pas, renseigne toi sur les fonctions asynchrones de C#. Je te propose d’utiliser la fonction Task.WaitAll, qui attend qu’un ensemble de tâches se terminent.

Voici un exemple de code qui illustre l’utilisation de cette fonction (je n’ai pas testé le code, il peut y avoir des erreurs) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
async Task<string> GetTitleAsync(Uri uri)
{
    // Récupère le titre de ta page web
    return title;
}

async void feed_Click(object sender, RoutedEventArgs e)
{
    string[] urls = File.ReadAllLines("pages.txt");
    var tasks = from url in urls select GetTitleAsync(new Uri(url));
    Task.WaitAll(tasks.ToArray());
    var titles = from task in tasks select task.Result;
}
+1 -0
Auteur du sujet

Re-bonjour et merci de vos réponses.

J’ai pu bidouiller un truc :

 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
        public static string result; // pour stocker les résultats
        public static int li;

        public static void ex(string u)
        {
            result += u + " " + DateTime.Now.ToString("HH:mm:ss ffffff") + "\r\n";
            li++;
            //System.Threading.Thread.Sleep(1000);
        }

        private async void go_Click(object sender, RoutedEventArgs e)
        {
            p.Value = 0; // une barre de progression
            ov.Text = null; // afficher les résultats en temps réels (pour les tests)
            string[] urls = XXX.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
            int c = urls.Length;
            p.Maximum = c;
            int mx = 5; // nombre max de threads à la fois

            List<Task> tasks = new List<Task>();
            for (int j = 0; j < c; j += mx)
            {
                int vmx = (j + mx > c) ? c : j + mx; ;
                result = null;

                for (int i = j; i < vmx; ++i)
                {
                    try
                    {
                        string v = urls[i];
                        tasks.Add(Task.Factory.StartNew(() => ex(v)));
                        p.Value = li; // on met à jour la barre de progression
                    }
                    catch { }
                }
                await Task.WhenAll(tasks);
                ov.Text += result; // on affiche les résultats "en direct"
                p.Value = li;
            }

        }

Un truc que j’avais oublié de préciser dans le premier post est que j’aimerais avoir une limite maximale (5, 15, 25) du nombre de threads qui se lancent. Ici c’est le int mx.

Ça marche à peu près, mon seul problème est que la manière de stocker les résultats est assez dégueulasse :D comme vous l’aurez constaté.

EDIT : j’avais pas vu la réponse de GaaH, je vais donc essayer ça plutôt que mon code fait à la va vite.

Par contre, y a-t-il moyen de spécifier le nombre minimum/maximum de tâches qui s’exécutent en même temps ?

Merci !

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte