2010-01-31 4 views
1

J'ai besoin de mettre en œuvre une file d'attente limitée par le producteur/consommateur, plusieurs consommateurs contre un seul producteur.Scénario de file d'attente bornée

J'ai une fonction de poussée qui ajoute un élément à la file d'attente, puis vérifie la taille maximale. Si nous l'avons atteint, renvoyez false, dans tous les autres cas, renvoyez true.

Dans le code suivant _vector est une liste <T>, onSignal consomme fondamentalement un élément d'une manière asynchrone.

Avez-vous des problèmes avec ce code?

public bool Push(T message) 
{ 
    bool canEnqueue = true; 

    lock (_vector) 
    { 
     _vector.Add(message); 
     if (_vector.Count >= _maxSize) 
     { 
      canEnqueue = false; 
     } 
    } 

    var onSignal = SignalEvent; 
    if (onSignal != null) 
    { 
     onSignal(); 
    } 

    return canEnqueue; 
} 

Répondre

1

Je sais que vous avez dit un seul producteur, plusieurs consommateurs, mais il convient de mentionner tout de même: si votre file d'attente est presque complète (par exemple 24 des 25 machines à sous), puis, si deux fils Push en même temps, vous finirez par dépasser la limite. S'il y a une chance que vous ayez plusieurs producteurs à un moment donné dans le futur, vous devriez envisager de faire Push un appel bloquant, et attendez un "disponible" AutoResetEvent qui est signalé après qu'un élément a été retiré ou après qu'un élément est mis en file d'attente alors qu'il reste des emplacements disponibles.

Le seul autre problème potentiel que je vois est le SignalEvent. Vous ne nous montrez pas la mise en œuvre de cela. Si elle est déclarée public event SignalEventDelegate SignalEvent, alors vous serez OK car le compilateur ajoute automatiquement un SynchronizedAttribute. Cependant, si SignalEvent utilise un délégué de support avec la syntaxe add/remove, alors vous devrez fournir votre propre verrouillage pour l'événement lui-même, sinon il sera possible pour un consommateur de se détacher de l'événement juste un peu trop tard et recevoir toujours un quelques signaux par la suite.

Modifier: En fait, c'est possible malgré tout; Plus important encore, si vous avez utilisé un délégué d'ajout/de suppression de style de propriété sans le verrouillage approprié, il est réellement possible que le délégué soit dans un état non valide lorsque vous essayez de l'exécuter. Même avec un événement synchronisé, les consommateurs doivent être prêts à recevoir (et ignorer) les notifications après leur désinscription. En dehors de cela, je ne vois pas de problèmes - bien que cela ne signifie pas qu'il n'y en a pas, cela signifie simplement que je n'en ai pas remarqué.

+0

Aaronaught, merci pour vos commentaires rapides pas de producteurs multiples dans un avenir prévisible, mais la taille maximale est également une suggestion lâche ... je n'ai pas besoin de l'appliquer strictement. SignalEvent est déclaré comme > événement interne Action SignalEvent; Je ne suis pas familier avec le SynchronizedAtribute ... va vérifier. Je construis ça en plus de retlang ... ça aide vraiment en termes de fournir un cadre général pour les applications multithread – lboregard

+0

@lboregard: Vous n'avez pas besoin de vous soucier de 'SynchronizedAttribute' si vous n'utilisez pas' ' Méthodes add'/'remove' sur le délégué - le compilateur les génère pour vous. C'est seulement lorsque vous remplacez le comportement par défaut que vous devez implémenter le verrouillage personnalisé. Cela semble être largement non documenté mais il est assez bien connu. – Aaronaught

1

Le plus gros problème que j'y vois est l'utilisation de List<T> pour implémenter une file d'attente; Il y a des problèmes de performances à cet égard, car la suppression du premier élément implique la copie de toutes les données.

Remarques supplémentaires; vous élevez le signal même si vous n'a pas ajouter des données, et l'utilisation des événements lui-même peut avoir un problème avec threading (il y a quelques cas de bord, même lorsque vous capturez la valeur avant le test null - plus il est éventuellement plus de frais généraux que d'utiliser le Monitor pour faire la signalisation).

Je passerais à un Queue<T> qui n'aura pas ce problème - ou mieux d'utiliser un exemple pré-roulé; par exemple Creating a blocking Queue in .NET?, qui fait exactement ce que vous discutez, et soutient un nombre quelconque de producteurs et de consommateurs. Il utilise l'approche de blocage, mais une approche « essayer » serait:

public bool TryEnqueue(T item) 
{ 
    lock (queue) 
    { 
     if (queue.Count >= maxSize) { return false; } 
     queue.Enqueue(item); 
     if (queue.Count == 1) 
     { 
      // wake up any blocked dequeue 
      Monitor.PulseAll(queue); 
     } 
     return true; 
    } 
} 

Enfin - ne pas vous « pousser » à une pile , pas une file d'attente?

+0

Marc, merci pour la réponse. Je n'ai pas mentionné que je devais être en mesure d'ajouter des éléments en haut de la "queue", donc une file d'attente droite ne m'aidera pas. cela devrait arriver rarement, mais j'apprécierais toute suggestion qui pourrait aider la performance. Je suis conscient des implémentations de files d'attente de blocage basées sur des moniteurs, mais j'utilise retlang comme framework de base pour gérer la synchronisation entre threads et j'apprends encore ses complexités, mais je vais revoir l'utilisation d'une telle file d'attente . – lboregard