web-dev-qa-db-fra.com

Implémentation du modèle de regroupement d'objets C #

Quelqu'un at-il une bonne ressource pour mettre en œuvre une stratégie de pool d'objets partagés pour une ressource limitée dans la veine du pooling de connexions SQL? (c.-à-d. serait pleinement implémenté qu'il soit thread-safe).

Pour donner suite à la demande de clarification de @Aaronaught, l'utilisation du pool serait destinée aux demandes d'équilibrage de charge adressées à un service externe. Pour le mettre dans un scénario qui serait probablement plus facile à comprendre immédiatement par opposition à ma situtation directe. J'ai un objet de session qui fonctionne de la même manière que l'objet ISession de NHibernate. Que chaque session unique gère sa connexion à la base de données. Actuellement, j'ai un objet de session qui dure depuis longtemps et je rencontre des problèmes pour lesquels mon fournisseur de service limite ma consommation d'utilisation de cette session individuelle.

Faute d'espérer qu'une seule session soit traitée comme un compte de service de longue durée, ils la traitent apparemment comme un client qui martèle leur service. Ce qui m'amène à ma question ici, au lieu d'avoir une session individuelle, je créerais un pool de différentes sessions et répartirais les demandes jusqu'au service sur plusieurs sessions au lieu de créer un point focal unique, comme je le faisais auparavant.

Espérons que cet arrière-plan offre une certaine valeur mais que vous puissiez répondre directement à certaines de vos questions:

Q: Les objets sont-ils coûteux à créer?
A: Aucun objet n'est un pool de ressources limitées

Q: Seront-ils acquis/diffusés très fréquemment?
A: Oui, encore une fois, on peut penser aux NHibernate ISessions où 1 est généralement acquis et libéré pour la durée de chaque demande de page.

Q: Un simple premier arrivé, premier servi, suffira-t-il ou avez-vous besoin de quelque chose de plus intelligent, c'est-à-dire qui empêcherait la famine?
A: Une simple distribution de type round robin suffirait, je suppose que vous voulez dire par famine s'il n'y a pas de sessions disponibles pour lesquelles les appelants sont bloqués dans l'attente des libérations. Ce n'est pas vraiment applicable puisque les sessions peuvent être partagées par différents appelants. Mon objectif est de répartir l'utilisation sur plusieurs sessions plutôt que sur une seule session.

Je pense que ceci est probablement une divergence par rapport à l'utilisation normale d'un pool d'objets. C'est pourquoi j'ai initialement laissé cette partie de côté et je n'avais prévu que d'adapter le motif pour permettre le partage d'objets plutôt que de permettre à une situation de famine de se produire.

Q: Qu'en est-il des choses comme les priorités, le paresseux par rapport au chargement rapide, etc.?
A: Il n'y a pas d'établissement de priorités, par souci de simplicité, supposons simplement que je créerais le pool d'objets disponibles lors de la création du pool lui-même.

158
Chris Marisic

Regroupement d'objets dans .NET Core

Le noyau dotnet a une implémentation du regroupement d'objets ajoutée à la bibliothèque de classes de base (BCL). Vous pouvez lire le numéro original de GitHub ici et voir le code pour System.Buffers . Actuellement, ArrayPool est le seul type disponible et est utilisé pour mettre en pool des tableaux. Il y a un article sur le blog de Nice ici .

namespace System.Buffers
{
    public abstract class ArrayPool<T>
    {
        public static ArrayPool<T> Shared { get; internal set; }

        public static ArrayPool<T> Create(int maxBufferSize = <number>, int numberOfBuffers = <number>);

        public T[] Rent(int size);

        public T[] Enlarge(T[] buffer, int newSize, bool clearBuffer = false);

        public void Return(T[] buffer, bool clearBuffer = false);
    }
}

Un exemple de son utilisation est visible dans ASP.NET Core. Comme il se trouve dans le BCL dotnet core, ASP.NET Core peut partager son pool d'objets avec d'autres objets tels que le sérialiseur JSON de Newtonsoft.Json. Vous pouvez lire this blog pour plus d'informations sur la façon dont Newtonsoft.Json fait cela.

Regroupement d'objets dans le compilateur Microsoft Roslyn C #

Le nouveau compilateur Microsoft Roslyn C # contient le type ObjectPool , qui est utilisé pour regrouper des objets fréquemment utilisés qui devraient normalement faire l’objet d’une nouvelle mise à jour et d’une collecte très variée. Cela réduit la quantité et la taille des opérations de récupération de place qui doivent être effectuées. Il existe quelques sous-implémentations différentes utilisant toutes ObjectPool (Voir: Pourquoi y a-t-il tant d'implémentations d'Object Pooling dans Roslyn? ).

1 - SharedPools - Stocke un pool de 20 objets ou 100 si le BigDefault est utilisé.

// Example 1 - In a using statement, so the object gets freed at the end.
using (PooledObject<Foo> pooledObject = SharedPools.Default<List<Foo>>().GetPooledObject())
{
    // Do something with pooledObject.Object
}

// Example 2 - No using statement so you need to be sure no exceptions are not thrown.
List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
// Do something with list
SharedPools.Default<List<Foo>>().Free(list);

// Example 3 - I have also seen this variation of the above pattern, which ends up the same as Example 1, except Example 1 seems to create a new instance of the IDisposable [PooledObject<T>][4] object. This is probably the preferred option if you want fewer GC's.
List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
try
{
    // Do something with list
}
finally
{
    SharedPools.Default<List<Foo>>().Free(list);
}

2 - ListPool et StringBuilderPool - Implémentations non strictement distinctes, mais wrappers autour de l'implémentation SharedPools illustrée ci-dessus spécifiquement pour List et StringBuilder. Donc, cela réutilise le pool d'objets stockés dans SharedPools.

// Example 1 - No using statement so you need to be sure no exceptions are thrown.
StringBuilder stringBuilder= StringBuilderPool.Allocate();
// Do something with stringBuilder
StringBuilderPool.Free(stringBuilder);

// Example 2 - Safer version of Example 1.
StringBuilder stringBuilder= StringBuilderPool.Allocate();
try
{
    // Do something with stringBuilder
}
finally
{
    StringBuilderPool.Free(stringBuilder);
}

3 - PooledDictionary et PooledHashSet - Ils utilisent directement ObjectPool et disposent d'un pool d'objets totalement séparé. Stocke un pool de 128 objets.

// Example 1
PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
// Do something with hashSet.
hashSet.Free();

// Example 2 - Safer version of Example 1.
PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
try
{
    // Do something with hashSet.
}
finally
{
    hashSet.Free();
}

Microsoft.IO.RecyclableMemoryStream

Cette bibliothèque permet la mise en commun des objets MemoryStream. C'est un remplacement instantané pour System.IO.MemoryStream. Il a exactement la même sémantique. Il a été conçu par les ingénieurs Bing. Lisez le blog ici ou voyez le code sur GitHub .

var sourceBuffer = new byte[]{0,1,2,3,4,5,6,7}; 
var manager = new RecyclableMemoryStreamManager(); 
using (var stream = manager.GetStream()) 
{ 
    stream.Write(sourceBuffer, 0, sourceBuffer.Length); 
}

Notez que RecyclableMemoryStreamManager doit être déclaré une fois et qu'il restera actif pendant tout le processus - il s'agit du pool. Il est parfaitement approprié d’utiliser plusieurs piscines si vous le souhaitez.

50

Cette question est un peu plus délicate que prévu en raison de plusieurs inconnues: le comportement de la ressource en pool, la durée de vie attendue/requise des objets, la vraie raison pour laquelle le pool est requis, etc. pools, pools de connexion, etc. - car il est plus facile d’en optimiser un lorsque vous savez exactement ce que fait la ressource et, plus important encore, avez control sur la façon dont cette ressource est implémentée.

Comme ce n’est pas si simple, j’ai essayé de proposer une approche assez flexible que vous pouvez expérimenter et voir ce qui fonctionne le mieux. Je m'excuse d'avance pour le post long, mais il reste encore beaucoup à faire pour mettre en place un pool de ressources polyvalent et décent. et je ne fais que gratter la surface.

Un pool à usage général devrait avoir quelques "paramètres" principaux, notamment:

  • Stratégie de chargement des ressources - avide ou paresseuse;
  • Chargement des ressources mécanisme - comment en construire un;
  • Stratégie d'accès - vous mentionnez "round robin" qui n'est pas aussi simple qu'il y paraît; cette implémentation peut utiliser un tampon circulaire qui est similaire, mais pas parfait, car le pool n'a aucun contrôle sur le moment où les ressources sont réellement récupérées. Les autres options sont FIFO et LIFO; FIFO aura plus d'un modèle d'accès aléatoire, mais LIFO le rend considérablement plus facile de mettre en œuvre une stratégie de libération la moins utilisée récemment (ce que vous avez dit était hors de portée, mais cela mérite quand même d'être mentionné).

Pour le mécanisme de chargement des ressources, .NET nous fournit déjà une abstraction propre: délégués.

private Func<Pool<T>, T> factory;

Passez ceci à travers le constructeur de la piscine et nous en avons presque terminé. L'utilisation d'un type générique avec une contrainte new() fonctionne également, mais elle est plus flexible.


Parmi les deux autres paramètres, la stratégie d'accès est la bête la plus complexe. Mon approche consistait donc à utiliser une approche basée sur l'héritage (interface):

public class Pool<T> : IDisposable
{
    // Other code - we'll come back to this

    interface IItemStore
    {
        T Fetch();
        void Store(T item);
        int Count { get; }
    }
}

Le concept ici est simple: nous allons laisser la classe publique Pool gérer les problèmes courants tels que la sécurité des threads, mais utiliser un "magasin d'objets" différent pour chaque modèle d'accès. LIFO est facilement représenté par une pile) FIFO est une file d'attente et j'ai utilisé un tampon circulaire non-très-optimisé-mais-probablement-adéquat implémentation utilisant un List<T> et un pointeur d'index pour se rapprocher d'un modèle d'accès alternatif.

Toutes les classes ci-dessous sont des classes internes du Pool<T> - c'était un choix de style, mais comme elles ne sont vraiment pas destinées à être utilisées en dehors du Pool, c'est le plus logique.

    class QueueStore : Queue<T>, IItemStore
    {
        public QueueStore(int capacity) : base(capacity)
        {
        }

        public T Fetch()
        {
            return Dequeue();
        }

        public void Store(T item)
        {
            Enqueue(item);
        }
    }

    class StackStore : Stack<T>, IItemStore
    {
        public StackStore(int capacity) : base(capacity)
        {
        }

        public T Fetch()
        {
            return Pop();
        }

        public void Store(T item)
        {
            Push(item);
        }
    }

Ce sont les plus évidentes - pile et file d'attente. Je ne pense pas qu'ils méritent vraiment beaucoup d'explications. Le tampon circulaire est un peu plus compliqué:

    class CircularStore : IItemStore
    {
        private List<Slot> slots;
        private int freeSlotCount;
        private int position = -1;

        public CircularStore(int capacity)
        {
            slots = new List<Slot>(capacity);
        }

        public T Fetch()
        {
            if (Count == 0)
                throw new InvalidOperationException("The buffer is empty.");

            int startPosition = position;
            do
            {
                Advance();
                Slot slot = slots[position];
                if (!slot.IsInUse)
                {
                    slot.IsInUse = true;
                    --freeSlotCount;
                    return slot.Item;
                }
            } while (startPosition != position);
            throw new InvalidOperationException("No free slots.");
        }

        public void Store(T item)
        {
            Slot slot = slots.Find(s => object.Equals(s.Item, item));
            if (slot == null)
            {
                slot = new Slot(item);
                slots.Add(slot);
            }
            slot.IsInUse = false;
            ++freeSlotCount;
        }

        public int Count
        {
            get { return freeSlotCount; }
        }

        private void Advance()
        {
            position = (position + 1) % slots.Count;
        }

        class Slot
        {
            public Slot(T item)
            {
                this.Item = item;
            }

            public T Item { get; private set; }
            public bool IsInUse { get; set; }
        }
    }

J'aurais pu choisir un certain nombre d'approches différentes, mais l'essentiel est que les ressources doivent être consultées dans le même ordre de création, ce qui signifie que nous devons conserver les références à ces ressources, mais les marquer comme "en cours d'utilisation" (ou non ). Dans le pire des cas, un seul emplacement est disponible et il faut une itération complète du tampon pour chaque extraction. C'est mauvais si vous avez mis en commun des centaines de ressources et que vous les acquérez et les publiez plusieurs fois par seconde; pas vraiment un problème pour un pool de 5-10 articles, et dans le cas typique, où les ressources sont faiblement utilisées, il ne lui reste plus qu’à gagner un ou deux créneaux.

N'oubliez pas que ces classes sont des classes internes privées - c'est pourquoi elles ne nécessitent pas beaucoup de vérification des erreurs, le pool lui-même en restreint l'accès.

Ajoutez une méthode d’énumération et d’usine et c’est terminé:

// Outside the pool
public enum AccessMode { FIFO, LIFO, Circular };

    private IItemStore itemStore;

    // Inside the Pool
    private IItemStore CreateItemStore(AccessMode mode, int capacity)
    {
        switch (mode)
        {
            case AccessMode.FIFO:
                return new QueueStore(capacity);
            case AccessMode.LIFO:
                return new StackStore(capacity);
            default:
                Debug.Assert(mode == AccessMode.Circular,
                    "Invalid AccessMode in CreateItemStore");
                return new CircularStore(capacity);
        }
    }

Le prochain problème à résoudre est la stratégie de chargement. J'ai défini trois types:

public enum LoadingMode { Eager, Lazy, LazyExpanding };

Les deux premiers devraient être explicites; le troisième est en quelque sorte un hybride, il charge paresseusement les ressources mais ne réutilise jamais les ressources tant que le pool n'est pas plein. Ce serait un bon compromis si vous voulez que le pool soit plein (ce qui semble être le cas), mais que vous souhaitiez différer le coût de leur création jusqu’au premier accès (c’est-à-dire pour améliorer les temps de démarrage).

Les méthodes de chargement ne sont vraiment pas trop compliquées, maintenant que nous avons l'abstraction item-store:

    private int size;
    private int count;

    private T AcquireEager()
    {
        lock (itemStore)
        {
            return itemStore.Fetch();
        }
    }

    private T AcquireLazy()
    {
        lock (itemStore)
        {
            if (itemStore.Count > 0)
            {
                return itemStore.Fetch();
            }
        }
        Interlocked.Increment(ref count);
        return factory(this);
    }

    private T AcquireLazyExpanding()
    {
        bool shouldExpand = false;
        if (count < size)
        {
            int newCount = Interlocked.Increment(ref count);
            if (newCount <= size)
            {
                shouldExpand = true;
            }
            else
            {
                // Another thread took the last spot - use the store instead
                Interlocked.Decrement(ref count);
            }
        }
        if (shouldExpand)
        {
            return factory(this);
        }
        else
        {
            lock (itemStore)
            {
                return itemStore.Fetch();
            }
        }
    }

    private void PreloadItems()
    {
        for (int i = 0; i < size; i++)
        {
            T item = factory(this);
            itemStore.Store(item);
        }
        count = size;
    }

Les champs size et count ci-dessus font référence à la taille maximale du pool et au nombre total de ressources possédées par le pool (mais pas nécessairement disponible), respectivement. AcquireEager est le plus simple, il suppose qu’un élément est déjà dans le magasin. Ces éléments seraient préchargés lors de la construction, c’est-à-dire dans la méthode PreloadItems indiquée en dernier.

AcquireLazy vérifie s'il existe des éléments libres dans le pool. Sinon, il en crée un nouveau. AcquireLazyExpanding créera une nouvelle ressource tant que le pool n'a pas encore atteint sa taille cible. J'ai essayé d'optimiser cela pour minimiser le verrouillage, et j'espère ne pas avoir commis d'erreur (j'ai l'ai testé dans des conditions de threads multiples, mais évidemment pas de manière exhaustive.

Vous vous demandez peut-être pourquoi aucune de ces méthodes ne se soucie de vérifier si le magasin a atteint la taille maximale. J'y viendrai dans un instant.


Maintenant pour la piscine elle-même. Voici l'ensemble complet des données privées, dont certaines ont déjà été présentées:

    private bool isDisposed;
    private Func<Pool<T>, T> factory;
    private LoadingMode loadingMode;
    private IItemStore itemStore;
    private int size;
    private int count;
    private Semaphore sync;

Répondant à la question que j’ai passée dans le dernier paragraphe - comment nous assurer de limiter le nombre total de ressources créées - il s’avère que le .NET dispose déjà d’un excellent outil pour cela, il s’appelle Semaphore et il est spécialement conçu pour permettre à un nombre fixe de threads d'accéder à une ressource (dans ce cas, la "ressource" est le magasin d'objets interne). Comme nous n'implémentons pas une file d'attente complète producteur/consommateur, cela répond parfaitement à nos besoins.

Le constructeur ressemble à ceci:

    public Pool(int size, Func<Pool<T>, T> factory,
        LoadingMode loadingMode, AccessMode accessMode)
    {
        if (size <= 0)
            throw new ArgumentOutOfRangeException("size", size,
                "Argument 'size' must be greater than zero.");
        if (factory == null)
            throw new ArgumentNullException("factory");

        this.size = size;
        this.factory = factory;
        sync = new Semaphore(size, size);
        this.loadingMode = loadingMode;
        this.itemStore = CreateItemStore(accessMode, size);
        if (loadingMode == LoadingMode.Eager)
        {
            PreloadItems();
        }
    }

Ne devrait pas y avoir de surprises ici. La seule chose à noter est le boîtier spécial pour un chargement rapide, en utilisant la méthode PreloadItems déjà montrée plus tôt.

Depuis que presque tout a été clairement extrait, les méthodes Acquire et Release sont vraiment très simples:

    public T Acquire()
    {
        sync.WaitOne();
        switch (loadingMode)
        {
            case LoadingMode.Eager:
                return AcquireEager();
            case LoadingMode.Lazy:
                return AcquireLazy();
            default:
                Debug.Assert(loadingMode == LoadingMode.LazyExpanding,
                    "Unknown LoadingMode encountered in Acquire method.");
                return AcquireLazyExpanding();
        }
    }

    public void Release(T item)
    {
        lock (itemStore)
        {
            itemStore.Store(item);
        }
        sync.Release();
    }

Comme expliqué précédemment, nous utilisons Semaphore pour contrôler les accès simultanés au lieu de vérifier religieusement l'état du magasin d'articles. Tant que les éléments acquis sont correctement libérés, vous n'avez pas à vous inquiéter.

Dernier point mais non le moindre, il y a le nettoyage:

    public void Dispose()
    {
        if (isDisposed)
        {
            return;
        }
        isDisposed = true;
        if (typeof(IDisposable).IsAssignableFrom(typeof(T)))
        {
            lock (itemStore)
            {
                while (itemStore.Count > 0)
                {
                    IDisposable disposable = (IDisposable)itemStore.Fetch();
                    disposable.Dispose();
                }
            }
        }
        sync.Close();
    }

    public bool IsDisposed
    {
        get { return isDisposed; }
    }

Le but de cette propriété IsDisposed deviendra clair dans un instant. Toute la méthode principale de Dispose consiste à éliminer les éléments mis en commun s’ils implémentent IDisposable.


Maintenant, vous pouvez fondamentalement utiliser ceci tel quel, avec un bloc try-finally, Mais je n'aime pas cette syntaxe, parce que si vous commencez à faire circuler des ressources en pool entre les classes et les méthodes, alors cela va devenir très confus. Il est possible que la classe principale qui utilise une ressource ne possède même pas have une référence au pool. Cela devient vraiment compliqué, alors une meilleure approche consiste à créer un objet pool "intelligent".

Disons que nous commençons par l'interface/classe simple suivante:

public interface IFoo : IDisposable
{
    void Test();
}

public class Foo : IFoo
{
    private static int count = 0;

    private int num;

    public Foo()
    {
        num = Interlocked.Increment(ref count);
    }

    public void Dispose()
    {
        Console.WriteLine("Goodbye from Foo #{0}", num);
    }

    public void Test()
    {
        Console.WriteLine("Hello from Foo #{0}", num);
    }
}

Voici notre prétendue ressource jetable Foo qui implémente IFoo et possède du code standard pour générer des identités uniques. Ce que nous faisons est de créer un autre objet spécial regroupé:

public class PooledFoo : IFoo
{
    private Foo internalFoo;
    private Pool<IFoo> pool;

    public PooledFoo(Pool<IFoo> pool)
    {
        if (pool == null)
            throw new ArgumentNullException("pool");

        this.pool = pool;
        this.internalFoo = new Foo();
    }

    public void Dispose()
    {
        if (pool.IsDisposed)
        {
            internalFoo.Dispose();
        }
        else
        {
            pool.Release(this);
        }
    }

    public void Test()
    {
        internalFoo.Test();
    }
}

Ceci projette juste toutes les "vraies" méthodes sur son IFoo interne (nous pourrions le faire avec une bibliothèque de proxy dynamique comme Castle, mais je ne vais pas entrer dans les détails). Il conserve également une référence au Pool qui le crée, de sorte que lorsque nous Dispose cet objet, il se libère automatiquement dans le pool. Sauf lorsque le pool a déjà été éliminé - cela signifie que nous sommes en mode "nettoyage" et dans ce cas, il nettoie le contenu interne. ressource à la place.


En utilisant l'approche ci-dessus, nous pouvons écrire du code comme ceci:

// Create the pool early
Pool<IFoo> pool = new Pool<IFoo>(PoolSize, p => new PooledFoo(p),
    LoadingMode.Lazy, AccessMode.Circular);

// Sometime later on...
using (IFoo foo = pool.Acquire())
{
    foo.Test();
}

C’est une bonne chose à faire très. Cela signifie que le code qui utilise le IFoo (par opposition au code qui le crée) n'a pas réellement besoin de connaître le pool. Vous pouvez même injecter des objets IFoo à l'aide de votre bibliothèque DI préférée et du Pool<T> En tant que fournisseur/fabrique.


J'ai mis le code complet sur Pastebin pour votre plus grand plaisir à copier-coller. Il existe également un court programme de test que vous pouvez utiliser pour jouer avec différents modes de chargement/accès et conditions multithread, afin de vous assurer qu'il est thread-safe et non buggy.

Faites-moi savoir si vous avez des questions ou des préoccupations à ce sujet.

305
Aaronaught

Quelque chose comme cela pourrait répondre à vos besoins.

/// <summary>
/// Represents a pool of objects with a size limit.
/// </summary>
/// <typeparam name="T">The type of object in the pool.</typeparam>
public sealed class ObjectPool<T> : IDisposable
    where T : new()
{
    private readonly int size;
    private readonly object locker;
    private readonly Queue<T> queue;
    private int count;


    /// <summary>
    /// Initializes a new instance of the ObjectPool class.
    /// </summary>
    /// <param name="size">The size of the object pool.</param>
    public ObjectPool(int size)
    {
        if (size <= 0)
        {
            const string message = "The size of the pool must be greater than zero.";
            throw new ArgumentOutOfRangeException("size", size, message);
        }

        this.size = size;
        locker = new object();
        queue = new Queue<T>();
    }


    /// <summary>
    /// Retrieves an item from the pool. 
    /// </summary>
    /// <returns>The item retrieved from the pool.</returns>
    public T Get()
    {
        lock (locker)
        {
            if (queue.Count > 0)
            {
                return queue.Dequeue();
            }

            count++;
            return new T();
        }
    }

    /// <summary>
    /// Places an item in the pool.
    /// </summary>
    /// <param name="item">The item to place to the pool.</param>
    public void Put(T item)
    {
        lock (locker)
        {
            if (count < size)
            {
                queue.Enqueue(item);
            }
            else
            {
                using (item as IDisposable)
                {
                    count--;
                }
            }
        }
    }

    /// <summary>
    /// Disposes of items in the pool that implement IDisposable.
    /// </summary>
    public void Dispose()
    {
        lock (locker)
        {
            count = 0;
            while (queue.Count > 0)
            {
                using (queue.Dequeue() as IDisposable)
                {

                }
            }
        }
    }
}

Exemple d'utilisation

public class ThisObject
{
    private readonly ObjectPool<That> pool = new ObjectPool<That>(100);

    public void ThisMethod()
    {
        var that = pool.Get();

        try
        { 
            // Use that ....
        }
        finally
        {
            pool.Put(that);
        }
    }
}
7
ChaosPandion
5
Thomas Mutzl

De retour dans la journée, Microsoft a fourni un cadre via Microsoft Transaction Server (MTS) et ultérieurement COM + pour effectuer le regroupement d’objets pour les objets COM. Cette fonctionnalité a été reportée sur System.EnterpriseServices dans .NET Framework et maintenant dans Windows Communication Foundation.

Regroupement d'objets dans WCF

Cet article est issu de .NET 1.1 mais devrait néanmoins s'appliquer dans les versions actuelles du Framework (même si WCF est la méthode préférée).

Regroupement d'objets .NET

4
Thomas

J'aime beaucoup la mise en œuvre d'Aronaught, en particulier dans la mesure où il gère l'attente d'une ressource pour devenir disponible via l'utilisation d'un sémaphore. Il y a plusieurs ajouts que j'aimerais faire:

  1. Remplacez sync.WaitOne() par sync.WaitOne(timeout) et exposez le délai en tant que paramètre de la méthode Acquire(int timeout). Cela nécessiterait également la gestion de la condition lorsque le thread a dépassé son délai d'attente pour qu'un objet soit disponible.
  2. Ajoutez la méthode Recycle(T item) pour gérer les situations dans lesquelles un objet doit être recyclé en cas de défaillance, par exemple.
4
Igor Pashchuk

Ceci est une autre implémentation, avec un nombre limité d'objets dans le pool.

public class ObjectPool<T>
    where T : class
{
    private readonly int maxSize;
    private Func<T> constructor;
    private int currentSize;
    private Queue<T> pool;
    private AutoResetEvent poolReleasedEvent;

    public ObjectPool(int maxSize, Func<T> constructor)
    {
        this.maxSize = maxSize;
        this.constructor = constructor;
        this.currentSize = 0;
        this.pool = new Queue<T>();
        this.poolReleasedEvent = new AutoResetEvent(false);
    }

    public T GetFromPool()
    {
        T item = null;
        do
        {
            lock (this)
            {
                if (this.pool.Count == 0)
                {
                    if (this.currentSize < this.maxSize)
                    {
                        item = this.constructor();
                        this.currentSize++;
                    }
                }
                else
                {
                    item = this.pool.Dequeue();
                }
            }

            if (null == item)
            {
                this.poolReleasedEvent.WaitOne();
            }
        }
        while (null == item);
        return item;
    }

    public void ReturnToPool(T item)
    {
        lock (this)
        {
            this.pool.Enqueue(item);
            this.poolReleasedEvent.Set();
        }
    }
}
3
user1609789

Orienté Java, cet article expose le modèle de pool connectionImpl et le modèle de pool d'objets abstraits et constitue une bonne première approche: http://www.developer.com/design/article.php/626171/Pattern-Summaries- Object-Pool.htm

Modèle de pool d'objets:

pattern

3
JoeBilly

Une extension de msdn explique comment créer un pool d'objets à l'aide d'un ConcurrentBag.

https://github.com/chivandikwa/ObjectPool

0