2010-12-08 2 views
0

J'essaye d'écrire une fonction qui démarre un fil pour chaque "contact", puis interroge (sur le réseau) les résultats de ce contact. Je veux que ma fonction d'attente attende au plus 1,5 seconde pour les réponses, et après cela, il suffit de terminer tous les threads restants. Le problème que je rencontre est que la fonction revient avant que tous les threads soient terminés, même si, selon la logique, cela ne devrait pas être possible. La boucle while dans la fonction principale doit être en attente jusqu'à ce que toutes les discussions ont complètement terminé, mais je reçois la sortie suivante:C# Condition de la race de filetage

FAIL: Storage test 1 exists 0 times in the DHT. 
    : Storage test 2 exists 0 times in the DHT. 
Added storage test 1 to the entries. 
Added storage test 2 to the entries. 

(Les lignes FAIL proviennent du programme d'essai principal de voir combien de résultats ont été retournés par Get (Selon ce que je peux voir, cela ne devrait pas être possible. Est-ce que quelqu'un sait où l'état de la course pourrait se produire (ou d'autres hypothèses que j'ai faites qui ne sont pas correctes)?

La définition de la fonction est en tant que telle:

public IList<Entry> Get(ID key) 
    { 
     ConcurrentBag<Entry> entries = new ConcurrentBag<Entry>(); 
     List<Thread> threads = new List<Thread>(); 
     foreach (Contact c in this.p_Contacts) 
     { 
      Thread t = new Thread(delegate() 
      { 
       try 
       { 
        FetchMessage fm = new FetchMessage(this, c, key); 
        fm.Send(); 
        int ticks = 0; 

        // Wait until we receive data, or timeout. 
        while (!fm.Received && ticks < 1500) 
        { 
         Thread.Sleep(100); 
         ticks += 100; 
        } 

        if (fm.Received) 
        { 
         foreach (Entry e in fm.Values) 
         { 
          Console.WriteLine("Added " + e.Value + " to the entries."); 
          entries.Add(e); 
         } 

         if (entries.Count == 0) 
          Console.WriteLine("There were no entries to add."); 
        } 
        else 
         Console.WriteLine("The node did not return in time."); 
       } 
       catch (Exception e) 
       { 
        Console.WriteLine(e); 
       } 
      } 
      ); 
      t.IsBackground = false; 
      t.Start(); 
     } 

     while (true) 
     { 
      bool stopped = true; 
      foreach (Thread t in threads) 
      { 
       if (t.ThreadState != ThreadState.Stopped) 
        stopped = false; 
      } 
      if (stopped) 
       break; 
      Thread.Sleep(100); 
     } 

     return new List<Entry>(entries.ToArray()); 
    } 
+1

Vous pouvez remplacer le cycle while (true) par un 'foreach (Thread t dans les threads) t.Join()', puis suivez les conseils de Toby en mettant 'threads.Add (t)' avant 't.Start () '[note: mon clavier n'a pas de touche de retour arrière, est-ce que quelqu'un peut corriger mon commentaire?] –

Répondre

5

On dirait que vous n'êtes jamais mettre le Thread (t) dans les List<Thread> (fils). Votre boucle foreach ne s'exécute jamais.

Le thread principal attend juste 100ms et continue.

+0

Ce que Toby a dit :) – Anton

+0

Heh, n'a même pas remarqué ça. Parfois, cela peut être le plus simple des problèmes: P –

0

Les threads ne sont pas ajoutés à votre liste de sorte que la boucle while se casse tout de suite?

0

La solution à ce problème est d'utiliser un ConcurrentDictionary pour garder une trace des contacts qui avaient leurs fils fini:

public IList<Entry> Get(ID key) 
    { 
     ConcurrentBag<Entry> entries = new ConcurrentBag<Entry>(); 
     ConcurrentDictionary<Contact, bool> done = new ConcurrentDictionary<Contact, bool>(); 
     List<Thread> threads = new List<Thread>(); 
     foreach (Contact c in this.p_Contacts) 
     { 
      Thread t; 
      ThreadStart ts = delegate() 
      { 
       try 
       { 
        FetchMessage fm = new FetchMessage(this, c, key); 
        fm.Send(); 
        int ticks = 0; 

        // Wait until we receive data, or timeout. 
        while (!fm.Received && ticks < 1500) 
        { 
         Thread.Sleep(100); 
         ticks += 100; 
        } 

        if (fm.Received) 
        { 
         foreach (Entry e in fm.Values) 
         { 
          Console.WriteLine("Added " + e.Value + " to the entries."); 
          entries.Add(e); 
         } 

         if (entries.Count == 0) 
          Console.WriteLine("There were no entries to add."); 
        } 
        else 
         Console.WriteLine("The node did not return in time."); 

        Thread.MemoryBarrier(); 
        done[c] = true; 
       } 
       catch (Exception e) 
       { 
        Console.WriteLine(e); 
       } 
      }; 
      t = new Thread(ts); 
      done[c] = false; 
      t.IsBackground = true; 
      t.Start(); 
     } 

     while (true) 
     { 
      bool stopped = true; 
      foreach (Contact c in this.p_Contacts) 
      { 
       if (!done[c]) 
        stopped = false; 
      } 
      if (stopped) 
       break; 
      Thread.Sleep(100); 
     } 

     return new List<Entry>(entries.ToArray()); 
    } 
2

@Toby a la bonne réponse, mais si je peux présenter d'autres choses à améliorer le code. Essentiellement, vous gérez manuellement votre propre ThreadPool et vos délais d'attente - et c'est quelque chose que .Net vous fournit dès la sortie de la boîte. Voir: http://msdn.microsoft.com/en-us/library/system.threading.threadpool(v=VS.100).aspx

Si vous combinez votre ThreadPool avec un .Net 4 Barrier, vous pouvez simplifier beaucoup le code. Essentiellement, une barrière bloquera tous les threads jusqu'à leur synchronisation. Lorsque vous passez la même barrière à vos threads et que vous effectuez une synchronisation à la fin, vous pouvez mettre en pause votre thread principal jusqu'à ce que tous les threads de travail soient terminés. Le code refondus ressemblerait à ceci:

// For the number of threads + 1 for the main thread 
Barrier barrier = new Barrier(this.p_Contacts.count + 1); 
ConcurrentBag<Entry> entries = new ConcurrentBag<Entry>(); 

foreach (Contact c in this.p_Contacts) 
{ 
    ThreadPool.RegisterWaitForSingleObject(
     new EventWaitHandle(false, EventResetMode.AutoReset), 
     (stateInfo,timedOut) => { 
      try 
      { 
       FetchMessage fm = new FetchMessage(this, c, key); 
       fm.Send(); 

       while(!fm.Received || !timedOut) 
       { 
        Thread.Sleep(100); 
       } 

       if(fm.Received) 
       { 
        foreach (Entry e in fm.Values) 
        { 
         entries.Add(e); 
         Console.WriteLine("Added " + e.Value + " to the entries."); 
        } 

        // avoid counting other thread's work 
        if (fm.Values.count == 0) 
        { 
         Console.WriteLine("There were no entries to add."); 
        } 
       } 
       else 
       { 
        Console.WriteLine("The node did not return in time."); 
       } 

       barrier.SignalAndWait(); 
      } 
      catch(Exception e) 
      { 
       Console.WriteLine(e); 
      } 
     }, null, TimeSpan.FromSeconds(1.5), true); 
    ); 
} 

// This limits total time waited to only 1.5 seconds 
barrier.SignalAndWait(TimeSpan.FromSeconds(1.5)); 

return new List<Entry>(entries.ToArray()); 

Au lieu de gérer manuellement les verrous de rotation comme vous faites, laissez .Net le faire pour vous.