170 votes

Création d’une file d’attente de blocage<T> dans .NET ?</T>

J'ai un scénario où j'ai plusieurs threads ajout d'une file d'attente et plusieurs threads de lecture à partir de la même file d'attente. Si la file d'attente atteint une taille spécifique de tous les threads qui sont le remplissage de la file d'attente sera bloqué sur ajouter jusqu'à un point est retiré de la file d'attente.

La solution ci-dessous est ce que je suis en ce moment et ma question est: Comment cela peut-il être amélioré? Est-ce un objet qui permet déjà ce comportement dans la BCL que je devrais utiliser?

internal class BlockingCollection<T> : CollectionBase, IEnumerable
{
    //todo: might be worth changing this into a proper QUEUE

    private AutoResetEvent _FullEvent = new AutoResetEvent(false);

    internal T this[int i]
    {
        get { return (T) List[i]; }
    }

    private int _MaxSize;
    internal int MaxSize
    {
        get { return _MaxSize; }
        set
        {
            _MaxSize = value;
            checkSize();
        }
    }

    internal BlockingCollection(int maxSize)
    {
        MaxSize = maxSize;
    }

    internal void Add(T item)
    {
        Trace.WriteLine(string.Format("BlockingCollection add waiting: {0}", Thread.CurrentThread.ManagedThreadId));

        _FullEvent.WaitOne();

        List.Add(item);

        Trace.WriteLine(string.Format("BlockingCollection item added: {0}", Thread.CurrentThread.ManagedThreadId));

        checkSize();
    }

    internal void Remove(T item)
    {
        lock (List)
        {
            List.Remove(item);
        }

        Trace.WriteLine(string.Format("BlockingCollection item removed: {0}", Thread.CurrentThread.ManagedThreadId));
    }

    protected override void OnRemoveComplete(int index, object value)
    {
        checkSize();
        base.OnRemoveComplete(index, value);
    }

    internal new IEnumerator GetEnumerator()
    {
        return List.GetEnumerator();
    }

    private void checkSize()
    {
        if (Count < MaxSize)
        {
            Trace.WriteLine(string.Format("BlockingCollection FullEvent set: {0}", Thread.CurrentThread.ManagedThreadId));
            _FullEvent.Set();
        }
        else
        {
            Trace.WriteLine(string.Format("BlockingCollection FullEvent reset: {0}", Thread.CurrentThread.ManagedThreadId));
            _FullEvent.Reset();
        }
    }
}

206voto

Marc Gravell Points 482669

Cela ressemble très dangereuse (synchronisation très peu) ; que diriez-vous quelque chose comme :

(edit)

En réalité, serait une façon de fermer la file d’attente afin que les lecteurs début sortant proprement - peut-être quelque chose comme un indicateur Boolean - si la valeur souhaitée, en file d’attente vide quelques retours (plutôt que de bloquer) :

64voto

xhafan Points 544

Utilisez .net 4 BlockingCollection, pour utilisation enqueue Add(), d’attente, utilisez Take(). Il utilise en interne non-bloquant ConcurrentQueue. Plus d’infos ici rapide et le meilleur producteur/consommateur file d’attente technique BlockingCollection vs file d’attente simultanée

15voto

Andreas Points 468

Vous pouvez utiliser le BlockingCollection et ConcurrentQueue dans le Namespace System.Collections.Concurrent

14voto

Daniel Earwicker Points 63298

"Comment cela peut-il être amélioré?"

Eh bien, vous avez besoin de regarder toutes les méthodes de votre classe et de demander ce qui se passerait si un autre thread a été simultanément l'appel de cette méthode ou toute autre méthode. Par exemple, vous avez mis un verrou dans la méthode Remove, mais pas dans la méthode Add. Qu'advient-il si un thread Ajoute en même temps qu'un autre thread Supprime? De mauvaises choses.

Considèrent également qu'une méthode peut retourner une deuxième objet qui permet d'accéder à la première de l'objet de données internes - par exemple, GetEnumerator. Imaginez un fil traverse que les agents recenseurs, un autre thread, c'est de modifier la liste en même temps. Pas bonne.

Une bonne règle de base est de faire le plus simple pour obtenir le droit de couper le nombre de méthodes dans la classe au minimum absolu.

En particulier, ne pas hériter d'une autre classe de conteneur, parce que vous allez vous exposer l'ensemble de cette classe de méthodes, fournissant un moyen pour l'appelant de corrompre la base de données interne, ou de voir partiellement effectuer les modifications apportées aux données (tout aussi mauvais, parce que les données s'affiche corrompu à ce moment). Masquer tous les détails et de se montrer impitoyable à propos de la façon dont vous autorisez l'accès.

J'aimerais vous conseillons fortement d'utiliser de solutions prêtes à l'emploi - un livre sur le filetage ou utiliser la 3ème partie de la bibliothèque. Autrement, compte tenu de ce que vous tentez, vous allez être le débogage du code pour un long moment.

Aussi, ne serait-il pas plus logique de Supprimer le retour d'un élément (par exemple, celui qui a été ajouté en premier, car il est une file d'attente), plutôt que de l'appelant de choisir un élément spécifique? Et lorsque la file d'attente est vide, peut-être Supprimer doit également bloquer.

Mise à jour: la réponse de Marc en fait met en œuvre toutes ces suggestions! :) Mais je vais laisser cela ici comme il peut être utile de comprendre pourquoi sa version est une amélioration.

5voto

Kevin Points 122

C’est ce que je suis op pour un thread-safe délimité file d’attente de blocage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X