2009-10-10 5 views
2

J'ai développé un "pool d'objets" et n'arrive pas à le faire sans utiliser Thread.Sleep() ce qui est "une mauvaise pratique" je crois.Pool d'objets partagés sans Thread.Sleep?

Cela concerne mon autre question "Is there a standard way of implementing a proprietary connection pool in .net?". L'idée derrière le pool d'objets est similaire à celle qui se trouve derrière le pool de connexions utilisé pour les connexions à la base de données. Cependant, dans mon cas, je l'utilise pour partager une ressource limitée dans un service Web ASP.NET standard (exécuté dans IIS6). Cela signifie que de nombreux threads demanderont l'accès à cette ressource limitée. Le pool afficherait ces objets (un «Get») et une fois que tous les objets de pool disponibles auront été utilisés, le thread suivant qui en demande un attendra simplement un certain laps de temps pour qu'un de ces objets redevienne disponible (un thread ferait . un « PUT » une fois fait avec l'objet) Si un objet ne devient pas disponible dans cette période de jeu, une erreur de temporisation se produira

Voici le code:.

public class SimpleObjectPool 
{ 
    private const int cMaxGetTimeToWaitInMs = 60000; 
    private const int cMaxGetSleepWaitInMs = 10; 
    private object fSyncRoot = new object(); 
    private Queue<object> fQueue = new Queue<object>(); 

    private SimpleObjectPool() 
    { 
    } 

    private static readonly SimpleObjectPool instance = new SimpleObjectPool(); 
    public static SimpleObjectPool Instance 
    { 
     get 
     { 
      return instance; 
     } 
    } 

    public object Get() 
    { 
     object aObject = null; 
     for (int i = 0; i < (cMaxGetTimeToWaitInMs/cMaxGetSleepWaitInMs); i++) 
     { 
      lock (fSyncRoot) 
      { 
       if (fQueue.Count > 0) 
       { 
        aObject = fQueue.Dequeue(); 
        break; 
       } 
      } 
      System.Threading.Thread.Sleep(cMaxGetSleepWaitInMs); 
     } 
     if (aObject == null) 
      throw new Exception("Timout on waiting for object from pool"); 
     return aObject; 
    } 

    public void Put(object aObject) 
    { 
     lock (fSyncRoot) 
     { 
      fQueue.Enqueue(aObject); 
     } 
    } 
} 

Pour utiliser l'utiliser, on ferait ce qui suit:

 public void ExampleUse() 
     { 
      PoolObject lTestObject = (PoolObject)SimpleObjectPool.Instance.Get(); 
      try 
      { 
       // Do something... 
      } 
      finally 
      { 
       SimpleObjectPool.Instance.Put(lTestObject); 
      } 
     } 

Maintenant la question que j'ai est: Comment est-ce que j'écris ceci pour que je me débarrasse du Thread.Sleep()?

(Pourquoi je veux faire cela parce que je pense qu'il est responsable du "faux" délai d'expiration que j'obtiens dans mes tests.Mon application de test a un pool d'objets avec 3 objets dedans. et chaque thread obtient 100 fois un objet du pool.Si le thread obtient un objet du pool, il le conserve si pendant 2000 ms, si ce n'est pas le cas, il passe à l'itération suivante. attendre 9 x 2 000 ms est 18 000 ms, ce qui est le temps maximum que chaque thread doit attendre pour un objet.Le délai d'attente de mon get est fixé à 60 000 ms, donc aucun thread ne doit jamais être temporisé. faites donc quelque chose qui ne va pas et je soupçonne que c'est le Thread.Sleep)

+0

Plusieurs lecteurs d'une seule ressource partagée sonne comme un sémaphore. Pourquoi ne l'utilisez-vous pas? – Amirshk

+1

D'autres ont répondu à ce que vous devriez faire. Je vais juste commenter pourquoi Sleep() est une mauvaise idée à utiliser ici - il n'y a rien qui vous garantit que lorsque le thread se réveille, il y aura un objet dans le pool. Ainsi, avec un peu de «chance», un thread peut continuer à dormir pendant que d'autres threads choisissent les objets regroupés. Ce dont vous avez besoin, c'est d'un mécanisme d'attente qui réveillera le fil lorsqu'un objet deviendra disponible. –

Répondre

5

Puisque vous utilisez déjà lock, c nvisager en utilisant Monitor.Wait et Monitor.Pulse

En Get():

lock (fSyncRoot) 
{ 
    while (fQueue.Count < 1) 
    Monitor.Wait(fSyncRoot); 

    aObject = fQueue.Dequeue(); 
} 

Et Put():

lock (fSyncRoot) 
{ 
    fQueue.Enqueue(aObject); 
    if (fQueue.Count == 1) 
     Monitor.Pulse(fSyncRoot); 
} 
+0

Une excellente solution simple qui ne change pas beaucoup mon code existant. Je devrais utiliser un sémaphore comme les autres l'ont expliqué, mais cela fonctionnera bien comme mes tests l'ont démontré. Merci Henk. – VinceJS

+0

C'est un (genre de) sémaphore. Monitor est un code entièrement géré et devrait être le premier choix, n'utilisez que quelque chose de «plus lourd» si nécessaire. –

2

vous devez utiliser un sémaphore.

http://msdn.microsoft.com/en-us/library/system.threading.semaphore.aspx

UPDATE: sémaphores sont l'une des constructions de base de programmation multi-threads. Un sémaphore peut être utilisé de différentes manières, mais l'idée de base est la suivante: lorsque vous disposez d'une ressource limitée et de nombreux clients qui souhaitent utiliser cette ressource, vous pouvez limiter le nombre de clients pouvant accéder à la ressource à tout moment.

ci-dessous est un exemple très brut. Je n'ai pas ajouté d'erreur de vérification ou d'essayer/finalement bloque mais vous devriez.

Vous pouvez également consulter: http://en.wikipedia.org/wiki/Semaphore_(programming)

Supposons que vous avez 10 seaux et 100 personnes qui veulent utiliser ces seaux. Nous pouvons représenter les compartiments dans une file d'attente.

Au début, ajouter tous vos seaux à la file d'attente

for(int i=0;i<10;i++) 
{ 
    B.Push(new Bucket()); 
} 

Maintenant, créez un sémaphores pour garder votre file d'attente du godet. Cette sémaphores est créé avec aucun élément déclenché et une capacité de 10.

Semaphore s = new Semaphore(0, 10); 

Tous les clients doivent vérifier la sémaphores avant d'accéder à la file d'attente. Vous pouvez avoir 100 threads exécutant la méthode thread ci-dessous. Les 10 premiers passeront le sémaphore. Tous les autres vont attendre.

void MyThread() 
{ 
    while(true) 
    { 
     // thread will wait until the semaphore is triggered once 
     // there are other ways to call this which allow you to pass a timeout 
     s.WaitOne(); 

     // after being triggered once, thread is clear to get an item from the queue 
     Bucket b = null; 

     // you still need to lock because more than one thread can pass the semaphore at the sam time. 
     lock(B_Lock) 
     { 
      b = B.Pop(); 
     } 

     b.UseBucket(); 

     // after you finish using the item, add it back to the queue 
     // DO NOT keep the queue locked while you are using the item or no other thread will be able to get anything out of it    
     lock(B_Lock) 
     { 
      B.Push(b); 
     } 

     // after adding the item back to the queue, trigger the semaphore and allow 
     // another thread to enter 
     s.Release(); 
    } 
} 
+0

Oui, je vois dans la documentation que vous avez raison, mais je n'arrive pas à comprendre. – VinceJS