2009-09-04 5 views
0

j'ai écrit une application multithread pour .NET et dans une partie très importante de code que je donne les résultats suivants:Problèmes de synchronisation: tout semble correct, mais

public class ContainerClass { 
    private object list_lock; 
    private ArrayList list; 
    private object init_lock = new object(); 
    private ThreadClass thread; 

    public void Start() { 
     lock(init_lock) { 
      if (thread == null) { 
       thread = new ThreadClass(); 
       ... 
      } 
     } 
    } 

    public void Stop() { 
     lock(init_lock) { 
      if (thread != null) { 
       thread.processList(0); 
       thread.finish(); 
       thread.waitUntilFinished(); 
       thread = null; 
      } else { 
       throw new ApplicationException("Assertion failed - already stopped."); 
      } 

      ... 
     } 
    } 

    private class ThreadedClass { 
     private ContainerClass container; 
     private Thread thread; 
     private bool finished; 
     private bool actually_finished; 

     public ThreadedClass(ContainerClass container) { 
      this.container = container; 
      thread = new Thread(run); 
      thread.IsBackground = true; 
      thread.Start(); 
     } 

     private void run() { 
      bool local_finished = false; 
      while (!local_finished) { 
       ArrayList to_process = null; 
       lock (container.list_lock) { 
        if (container.list.Count > 0) { 
         to_process = new ArrayList(); 
         to_process.AddRange(container.list); 
        } 
       } 
       if (to_process == null) { 
        // Nothing to process so wait 
        lock (this) { 
         if (!finished) { 
          try { 
           Monitor.Wait(this); 
          } catch (ThreadInterruptedException) { 
          } 
         } 
        } 
       } else if (to_process.Count > 0) { 
        // Something to process, so go ahead and process the journals, 
        int sz = to_process.Count; 
        // For all elements 
        for (int i = 0; i < sz; ++i) { 
         // Pick the lowest element to process 
         object obj = to_process[i]; 
         try { 
          // process the element... 
          ... 
         } catch (IOException e) { 
          ... 
          // If there is an error processing the best thing to do is finish 
          lock (this) { 
           finished = true; 
          } 
         } 
        } 
       } 

       lock (this) { 
        local_finished = finished; 
        // Remove the elements that we have just processed. 
        if (to_process != null) { 
         lock (container.list_lock) { 
          int sz = to_process.Count; 
          for (int i = 0; i < sz; ++i) { 
           container.list.RemoveAt(0); 
          } 
         } 
        } 
        // Notify any threads waiting 
        Monitor.PulseAll(this); 
       } 
      } 

      lock (this) { 
       actually_finished = true; 
       Monitor.PulseAll(this); 
      } 
     } 

     public void waitUntilFinished() { 
      lock (this) { 
       try { 
        while (!actually_finished) { 
         Monitor.Wait(this); 
        } 
       } catch (ThreadInterruptedException e) { 
        throw new ApplicationException("Interrupted: " + e.Message); 
       } 
      } 
     } 

     public void processList(int until_size) { 
      lock (this) { 
       Monitor.PulseAll(this); 
       int sz; 
       lock (container.list_lock) { 
        sz = container.list.Count; 
       } 
       // Wait until the sz is smaller than 'until_size' 
       while (sz > until_size) { 
        try { 
         Monitor.Wait(this); 
        } catch (ThreadInterruptedException) { 
        } 
        lock (container.list_lock) { 
         sz = container.list.Count; 
        } 
       } 
      } 
     } 
    } 
} 

Comme vous pouvez le voir, le thread jusqu'à ce que la collection est vide mais il semble que les conflits de synchronisation interdisent au thread d'entrer au point (le seul dans le code entier) où un élément est retiré de la collection list dans le ContainerClass. Ce choc provoque le code pour ne jamais revenir et l'application de continuer à fonctionner si la méthode processList est appelée avec la valeur de until_size de 0.

Je vous demande mieux développeur que moi (et je pense qu'il ya beaucoup là-bas) pour m'aider à corriger ce petit morceau de code, car je ne comprends vraiment pas pourquoi la liste n'est pas décrémentée ...

Merci beaucoup du fond du coeur.

PS. Je voudrais souligner que le code fonctionne parfaitement pour tout le temps: la seule situation dans laquelle il freine c'est en appelant thread.processList(0) de ContainerClass.Stop().

+1

Pourquoi ce wiki communautaire? – scottm

+0

parce que je ne sais pas ce que «wiki communautaire» signifie dans StackOverflow et je pensais que c'était un moyen de recevoir plus de visibilité ...: P – Antonello

+0

Vous ne recevez pas de points de rep pour répondre. Découvrez la faq en haut de la page. – scottm

Répondre

1

Le problème peut-il être que vous verrouillez l'objet ThreadClass lui-même plutôt qu'un objet de synchronisation?

Essayez d'ajouter une autre variable privée pour verrouiller le:

private static readonly object lockObject = new object() 

et remplacer tous les appels de lock(this) avec lock(lockObject)

MSDN conseille clairement contre ce que vous faites:

En général, évitez de verrouiller un type public ou des instances au-delà du contrôle de votre code. Les constructions communes verrouillent (ce), verrouillage (typeof (MyType)) et verrouillage ("myLock") ne respectent pas cette directive :

lock (this) is a problem if the instance can be accessed publicly. 

Edit:

Je pense que je voir une situation de blocage. Si vous appelez exécutez() quand il n'y a pas d'objet à traiter, ou vous arrivez à aucun objet à traiter, vous verrouillez (ce), puis appelez Monitor.Wait (ce) et le thread:

 if (to_process == null) { 
      // Nothing to process so wait 
      lock (this) { /* nothing's going to get this lock again until Monitor.PulseAll(this) is called from somewhere */ 
       if (!finished) { 
        try { 
         Monitor.Wait(this); /* thread is waiting for Pulse(this) or PulseAll(this) */ 
        } catch (ThreadInterruptedException) { 
        } 
       } 
      } 
     } 

Si vous êtes dans cette condition lorsque vous appelez Container.Stop(), lorsque ThreadProcess.processList (int) est appelé, vous appelez lock (this) à nouveau, qui ne peut pas entrer dans la section car la méthode run() a toujours le verrou:

lock (this) { /* run still holds this lock, waiting for PulseAll(this) to be called */ 
       Monitor.PulseAll(this); /* this isn't called so run() never continues */ 
       int sz; 
       lock (container.list_lock) { 
        sz = container.list.Count; 
       } 

Ainsi, Monitor.PulseAll() ne peut pas être appelé à libérer le fil d'attente dans la méthode run() pour quitter la zone de blocage (cela), ils sont dans l'impasse attendent les uns des autres. Droite?

+0

désolé de dire que cette solution ne change pas la situation ... merci quand même d'essayer ... :) – Antonello

+0

@Antonello, pas de problème. Supprimez le wiki de la communauté (cliquez sur Modifier, décochez le wiki de la communauté). – scottm

0

Je pense que vous devriez essayer d'expliquer mieux ce que vous voulez vraiment réaliser.Cela semble très étrange, car vous devriez appeler Monitor.Pulse lorsque vous changez l'état de verrouillage et non lorsque vous commencez à verrouiller. Où créez-vous les threads de travail - cette section n'est pas claire car je vois seulement Thread.Start()? Btw Je vous conseille de regarder PowerCollections - peut-être vous trouvez ce que vous avez besoin là.

+0

J'ai mis à jour l'exemple de code pour inclure les points dans le ContainerClass où le ThreadClass est instancié et appelé. Par ailleurs, la méthode "processList" notifie tous les threads en attente juste après l'acquisition du verrou pour accéder à d'autres parties du code verrouillé sur "this". – Antonello

+0

Votre relevé ne fonctionne pas. La description dit pour PulseAll dit "Notifie un thread dans la file d'attente d'un changement dans l'état de l'objet verrouillé." - mais cela ne change que si le verrou est libéré. – weismat

+0

J'ai peut-être fait quelque chose de pas cohérent (bien que j'ai suivi l'exemple dans la bibliothèque MSDN pour Monitor.Pulse: http://msdn.microsoft.com/en-us/library/system.threading.monitor.pulse.aspx), mais cela fonctionne et quand j'ai essayé de l'enlever, tout s'est écrasé ... – Antonello

Questions connexes