wake-up-neo.net

Implementierung eines C # -Objekt-Pooling-Musters

Hat jemand eine gute Ressource für die Implementierung einer Shared Object Pool-Strategie für eine begrenzte Ressource im Sinne von SQL Connection Pooling? (dh würde vollständig implementiert, dass es threadsicher ist).

Zur Nachverfolgung der @Aaronaught-Anforderung zur Klärung wird der Pool für Lastenausgleichsanforderungen an einen externen Service verwendet. Um es in ein Szenario zu bringen, das wahrscheinlich einfacher zu verstehen wäre als meine direkte Situation. Ich habe ein Sitzungsobjekt, das ähnlich wie das Objekt ISession von NHibernate funktioniert. Dass jede einzelne Sitzung die Verbindung zur Datenbank verwaltet. Derzeit habe ich ein Sitzungsobjekt mit langer Laufzeit und es treten Probleme auf, bei denen mein Dienstanbieter die Nutzung dieser einzelnen Sitzung durch meine Gebühren einschränkt.

Aufgrund der fehlenden Erwartung, dass eine einzelne Sitzung als ein langjähriges Dienstkonto behandelt wird, wird sie anscheinend als ein Client behandelt, der ihren Dienst beeinträchtigt. Damit komme ich hier zu meiner Frage: Anstatt eine einzelne Sitzung zu haben, würde ich einen Pool von verschiedenen Sitzungen erstellen und die Anforderungen auf den Dienst auf diese mehreren Sitzungen aufteilen, anstatt wie zuvor einen einzigen Schwerpunkt zu erstellen.

Hoffentlich bietet dieser Hintergrund einen gewissen Wert, aber um einige Ihrer Fragen direkt zu beantworten:

F: Sind die Objekte in der Herstellung teuer?
A: Keine Objekte sind ein Pool begrenzter Ressourcen

F: Werden sie sehr häufig erworben/freigegeben?
A: Ja, man kann sich wieder NHibernate ISessions vorstellen, bei denen normalerweise 1 für die Dauer jeder einzelnen Seitenanforderung erfasst und freigegeben wird.

F: Reicht ein einfaches Werde-Werde-Werde aus oder brauchen Sie etwas Intelligenteres, d. H. Das würde den Hunger verhindern?
A: Eine einfache Round-Robin-Verteilung würde ausreichen. Ich nehme an, Sie meinen, wenn keine Sitzungen verfügbar sind, werden Anrufer blockiert und warten auf Freigaben. Dies gilt nicht wirklich, da die Sitzungen von verschiedenen Anrufern geteilt werden können. Mein Ziel ist es, die Nutzung auf mehrere Sitzungen anstatt auf eine einzige Sitzung zu verteilen.

Ich glaube, dies ist wahrscheinlich eine Abweichung von der normalen Verwendung eines Objektpools, weshalb ich diesen Teil ursprünglich ausgelassen und geplant habe, nur das Muster anzupassen, um das Teilen von Objekten zu ermöglichen, anstatt zuzulassen, dass jemals eine Hungersituation auftritt.

F: Was ist mit Prioritäten, faulem oder eifrigem Laden usw.?
A: Es ist keine Priorisierung erforderlich. Der Einfachheit halber gehe ich nur davon aus, dass ich den Pool verfügbarer Objekte beim Erstellen des Pools selbst erstellen würde.

158
Chris Marisic

Objektpooling in .NET Core

In dotnet core wurde eine Implementierung des Objektpoolings zur Basisklassenbibliothek (BCL) hinzugefügt. Sie können das ursprüngliche GitHub-Problem lesen hier und den Code für System.Buffers anzeigen. Derzeit ist ArrayPool der einzige verfügbare Typ und wird zum Poolen von Arrays verwendet. Es gibt einen schönen Blog-Beitrag hier .

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);
    }
}

Ein Beispiel für die Verwendung ist in ASP.NET Core zu sehen. Da es sich in der Dotnet Core-BCL befindet, kann ASP.NET Core den Objektpool für andere Objekte wie den JSON-Serializer von Newtonsoft.Json freigeben. Sie können this Blogpost lesen, um weitere Informationen darüber zu erhalten, wie Newtonsoft.Json dies tut.

Objektpooling im Microsoft Roslyn C # -Compiler

Der neue Microsoft Roslyn C # -Compiler enthält den Typ ObjectPool , der verwendet wird, um häufig verwendete Objekte zu bündeln, die normalerweise neu erstellt und sehr oft Müll gesammelt werden. Dies reduziert die Menge und die Größe von Speicherbereinigungsvorgängen, die auftreten müssen. Es gibt einige verschiedene Unterimplementierungen, die alle ObjectPool verwenden (Siehe: Warum gibt es in Roslyn so viele Implementierungen von Object Pooling? ).

1 - SharedPools - Speichert einen Pool von 20 Objekten oder 100, wenn der BigDefault verwendet wird.

// 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 und StringBuilderPool - Nicht streng voneinander getrennte Implementierungen, sondern Wrapper um die oben für List und StringBuilder gezeigte SharedPools-Implementierung. Dadurch wird der in SharedPools gespeicherte Pool von Objekten wiederverwendet.

// 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 und PooledHashSet - Diese verwenden ObjectPool direkt und verfügen über einen völlig separaten Pool von Objekten. Speichert einen Pool von 128 Objekten.

// 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

Diese Bibliothek bietet Pooling für MemoryStream Objekte. Es ist ein Ersatz für System.IO.MemoryStream. Es hat genau die gleiche Semantik. Es wurde von Bing-Ingenieuren entworfen. Lesen Sie den Blog-Beitrag hier oder lesen Sie den Code auf 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); 
}

Beachten Sie, dass RecyclableMemoryStreamManager einmal deklariert werden sollte und für den gesamten Prozess gültig ist - dies ist der Pool. Wenn Sie möchten, können Sie auch mehrere Pools verwenden.

50

Diese Frage ist aufgrund mehrerer Unbekannter etwas kniffliger als erwartet: Das Verhalten der gepoolten Ressource, die erwartete/erforderliche Lebensdauer von Objekten, der wahre Grund, warum der Pool erforderlich ist, usw. Normalerweise handelt es sich bei Pools um Spezial-Threads Pools, Verbindungspools usw. - weil es einfacher ist, einen zu optimieren, wenn Sie genau wissen, was die Ressource tut und vor allemcontrolüber die Implementierung dieser Ressource verfügen.

Da es nicht so einfach ist, habe ich versucht, einen ziemlich flexiblen Ansatz anzubieten, mit dem Sie experimentieren und sehen können, was am besten funktioniert.Wir entschuldigen uns im Voraus für den langen Beitrag, aber es gibt noch viel zu bedecken, wenn es um die Implementierung eines angemessenen Allzweck-Ressourcenpools geht. und ich kratz wirklich nur die oberfläche.

Ein Allzweckpool müsste einige "Haupteinstellungen" haben, darunter:

  • Strategie zum Laden von Ressourcen - eifrig oder faul;
  • Laden von Ressourcenmechanism- wie man tatsächlich eine erstellt;
  • Zugriffsstrategie - Sie erwähnen "Round Robin", was nicht so einfach ist, wie es sich anhört. Diese Implementierung kann einen Ringpuffer verwenden, dersimilarist, aber nicht perfekt, da der Pool keine Kontrolle darüber hat, wann Ressourcen tatsächlich zurückgefordert werden. Andere Optionen sind FIFO und LIFO; FIFO haben eher ein Direktzugriffsmuster, aber LIFO macht es bedeutend Es ist einfacher, eine zuletzt verwendete Freigabestrategie zu implementieren (von der Sie sagten, dass sie nicht in den Geltungsbereich fällt, aber dennoch erwähnenswert ist).

Für den Mechanismus zum Laden von Ressourcen gibt uns .NET bereits eine saubere Abstraktionsdelegierte.

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

Führen Sie dies durch den Konstruktor des Pools und wir sind damit fertig. Die Verwendung eines generischen Typs mit einer new() -Einschränkung funktioniert ebenfalls, ist jedoch flexibler.


Von den beiden anderen Parametern ist die Zugriffsstrategie das kompliziertere Tier. Daher bestand mein Ansatz darin, einen auf Vererbung (Schnittstelle) basierenden Ansatz zu verwenden:

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

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

Das Konzept hier ist einfach: Wir lassen die öffentliche Klasse Pool die allgemeinen Probleme wie Threadsicherheit behandeln, verwenden jedoch für jedes Zugriffsmuster einen anderen "Item Store". LIFO wird leicht durch einen Stapel dargestellt, FIFO ist eine Warteschlange, und ich habe einen nicht sehr optimierten, aber wahrscheinlich angemessenen Umlaufpuffer verwendet Implementierung mit einem List<T> und einem Indexzeiger, um ein Round-Robin-Zugriffsmuster zu approximieren.

Alle unten aufgeführten Klassen sind innere Klassen des Pool<T> - dies war eine Stilauswahl, aber da diese wirklich nicht außerhalb des Pool verwendet werden sollen, ist dies am sinnvollsten.

    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);
        }
    }

Dies sind die offensichtlichen - Stapel und Warteschlange. Ich denke nicht, dass sie wirklich viel Erklärung verdienen. Der Ringpuffer ist etwas komplizierter:

    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; }
        }
    }

Ich hätte verschiedene Ansätze wählen können, aber das Endergebnis ist, dass auf Ressourcen in derselben Reihenfolge zugegriffen werden sollte, in der sie erstellt wurden. Dies bedeutet, dass wir Verweise auf sie beibehalten müssen, sie aber als "in Verwendung" markieren müssen (oder nicht) ). Im schlimmsten Fall ist immer nur ein Slot verfügbar, und für jeden Abruf wird eine vollständige Iteration des Puffers benötigt. Dies ist schlecht, wenn Sie Hunderte von Ressourcen gepoolt haben und diese mehrmals pro Sekunde erwerben und freigeben. Bei einem Pool von 5 bis 10 Elementen ist dies kein wirkliches Problem. In dem Falltypical, in dem die Ressourcen nur wenig genutzt werden, müssen nur ein oder zwei Slots vorgerückt werden.

Denken Sie daran, dass es sich bei diesen Klassen um private innere Klassen handelt. Aus diesem Grund ist keine umfassende Fehlerprüfung erforderlich, da der Pool selbst den Zugriff auf diese Klassen einschränkt.

Wenn Sie eine Aufzählung und eine Factory-Methode eingeben, ist dieser Teil erledigt:

// 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);
        }
    }

Das nächste zu lösende Problem ist die Ladestrategie. Ich habe drei Typen definiert:

public enum LoadingMode { Eager, Lazy, LazyExpanding };

Die ersten beiden sollten selbsterklärend sein; Der dritte ist eine Art Hybrid, der Ressourcen faul lädt, jedoch keine Ressourcen wiederverwendet, bis der Pool voll ist. Dies wäre ein guter Kompromiss, wenn Sie möchten, dass der Pool voll ist (wie es sich anhört), aber die Kosten für die tatsächliche Erstellung bis zum ersten Zugriff aufgeschoben werden sollen (d. H. Um die Startzeiten zu verbessern).

Die Lademethoden sind nicht allzu kompliziert, da wir nun die Item-Store-Abstraktion haben:

    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;
    }

Die obigen Felder size und count beziehen sich auf die maximale Größe des Pools und die Gesamtzahl der Ressourcen, deren Eigentümer der Pool ist (jedoch nicht unbedingtavailable). beziehungsweise. AcquireEager ist am einfachsten, es wird davon ausgegangen, dass sich ein Artikel bereits im Geschäft befindet - diese Artikel werden bei der Erstellung vorgeladen, d. h. in der zuletzt gezeigten PreloadItems -Methode.

AcquireLazy prüft, ob sich freie Elemente im Pool befinden. Andernfalls wird ein neues erstellt. AcquireLazyExpanding erstellt eine neue Ressource, solange der Pool seine Zielgröße noch nicht erreicht hat. Ich habe versucht, dies zu optimieren, um das Sperren zu minimieren, und ich hoffe, dass ich keine Fehler gemacht habe (ichhabedies unter Multithread-Bedingungen getestet, aber offensichtlich nicht erschöpfend).

Sie fragen sich möglicherweise, warum sich keine dieser Methoden darum kümmert, zu überprüfen, ob das Geschäft die maximale Größe erreicht hat. Ich werde gleich darauf zurückkommen.


Nun zum Pool selbst. Hier ist der vollständige Satz privater Daten, von denen einige bereits gezeigt wurden:

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

Bei der Beantwortung der Frage, die ich im letzten Absatz beschönigt habe - wie wir sicherstellen können, dass die Gesamtzahl der erstellten Ressourcen begrenzt wird - stellt sich heraus, dass .NET bereits ein perfektes Werkzeug dafür hat. Es heißt Semaphore und Es wurde speziell entwickelt, um einer festen Anzahl von Threads den Zugriff auf eine Ressource zu ermöglichen (in diesem Fall ist die "Ressource" der innere Elementspeicher). Da wir keine vollständige Produzenten-/Konsumentenwarteschlange implementieren, ist dies für unsere Anforderungen vollkommen ausreichend.

Der Konstruktor sieht folgendermaßen aus:

    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();
        }
    }

Sollte hier keine Überraschungen geben. Zu beachten ist nur das spezielle Gehäuse für eifriges Laden mit der zuvor bereits gezeigten Methode PreloadItems.

Da mittlerweile fast alles sauber abstrahiert ist, sind die tatsächlichen Methoden Acquire und Release wirklich sehr einfach:

    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();
    }

Wie bereits erläutert, verwenden wir Semaphore, um die Parallelität zu steuern, anstatt den Status des Gegenstandsspeichers religiös zu überprüfen. Solange die erworbenen Gegenstände korrekt freigegeben sind, besteht kein Grund zur Sorge.

Last but not least gibt es Aufräumarbeiten:

    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; }
    }

Der Zweck dieser IsDisposed -Eigenschaft wird in einem Moment klar. Alles, was die Hauptmethode Dispose wirklich tut, ist, die tatsächlich gepoolten Elemente zu entsorgen, wenn sie IDisposable implementieren.


Jetzt können Sie diese Funktion im Grunde genommen mit einem try-finally - Block verwenden, aber ich mag diese Syntax nicht, denn wenn Sie anfangen, gepoolte Ressourcen zwischen Klassen und Methoden zu verteilen, wird es sehr verwirrend. Es ist möglich, dass die Hauptklasse, die eine Ressource verwendet, nicht einmaleinen Verweis auf den Pool hat. Es wird wirklich ziemlich chaotisch, daher ist es ein besserer Ansatz, ein "intelligentes" zusammengefasstes Objekt zu erstellen.

Angenommen, wir beginnen mit der folgenden einfachen Schnittstelle/Klasse:

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);
    }
}

Hier ist unsere vorgetäuschte Einweg-Ressource Foo, die IFoo implementiert und einen Code für die Erstellung eindeutiger Identitäten enthält. Was wir tun, ist ein anderes spezielles, zusammengefasstes Objekt zu erstellen:

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();
    }
}

Dies überträgt einfach alle "echten" Methoden auf das innere IFoo (wir könnten dies mit einer Dynamic Proxy-Bibliothek wie Castle tun, aber ich werde nicht darauf eingehen). Es behält auch einen Verweis auf das Pool bei, das es erstellt. Wenn wir dieses Objekt Dispose verwenden, gibt es sich automatisch in den Pool zurück. Außer , wenn der Pool bereits entsorgt wurde - dies bedeutet, dass wir uns im "Aufräum" -Modus befinden und in diesem Fall tatsächlichdie interne Ressource aufräumenstattdessen.


Mit dem obigen Ansatz können wir Code wie folgt schreiben:

// 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();
}

Dies ist einesehrgute Sache zu können. Dies bedeutet, dass der Code, derthe IFoo verwendet (im Gegensatz zu dem Code, der ihn erstellt), den Pool nicht unbedingt zu kennen braucht. Sie können sogar IFoo Objekte mit Ihrer bevorzugten DI-Bibliothek und dem Pool<T> Als Provider/Factory einfügen.


Ich habe den vollständigen Code auf Pastebin für Ihr Vergnügen beim Kopieren und Einfügen eingefügt. Es gibt auch ein kurzes Testprogramm , mit dem Sie mit verschiedenen Lade-/Zugriffsmodi und Multithread-Bedingungen herumspielen können, um sich davon zu überzeugen, dass es thread-sicher und nicht fehlerhaft ist.

Lassen Sie mich wissen, wenn Sie irgendwelche Fragen oder Bedenken dazu haben.

305
Aaronaught

So etwas könnte Ihren Bedürfnissen entsprechen.

/// <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)
                {

                }
            }
        }
    }
}

Beispielnutzung

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

Früher stellte Microsoft über Microsoft Transaction Server (MTS) und später COM + ein Framework bereit, um das Objektpooling für COM-Objekte durchzuführen. Diese Funktionalität wurde in .NET Framework und jetzt in Windows Communication Foundation auf System.EnterpriseServices übertragen.

Objektpooling in WCF

Dieser Artikel stammt aus .NET 1.1, sollte jedoch in den aktuellen Versionen des Frameworks weiterhin gelten (obwohl WCF die bevorzugte Methode ist).

Object Pooling .NET

4
Thomas

Die Implementierung von Aronaught gefällt mir sehr gut - zumal er die Wartezeit auf die Verfügbarkeit der Ressource mithilfe eines Semaphors bewältigt. Es gibt mehrere Ergänzungen, die ich machen möchte:

  1. Ändern Sie sync.WaitOne() in sync.WaitOne(timeout) und legen Sie das Timeout als Parameter für die Acquire(int timeout) -Methode offen. Dies würde auch die Behandlung der Bedingung erforderlich machen, wenn das Zeitlimit für das Warten auf die Verfügbarkeit eines Objekts abläuft.
  2. Fügen Sie die Recycle(T item) -Methode hinzu, um beispielsweise Situationen zu behandeln, in denen ein Objekt wiederverwendet werden muss, wenn ein Fehler auftritt.
4
Igor Pashchuk

Dies ist eine weitere Implementierung mit einer begrenzten Anzahl von Objekten im 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

In diesem Artikel werden das Muster des connectionImpl-Pools und das Muster des Pools für abstrahierte Objekte Java-orientiert dargestellt. Dies könnte ein guter erster Ansatz sein: http://www.developer.com/design/article.php/626171/Pattern -Summaries-Object-Pool.htm

Muster des Objektpools:

pattern

3
JoeBilly

Eine Erweiterung von MSDN zum Erstellen eines Objektpools mit ConcurrentBag.

https://github.com/chivandikwa/ObjectPool

0