Реализация шаблона пула объектов C # - PullRequest
158 голосов
/ 24 марта 2010

Есть ли у кого-нибудь хороший ресурс по реализации стратегии общего пула объектов для ограниченного ресурса в духе пула соединений Sql? (т. е. будет полностью реализовано, что это потокобезопасно).

Чтобы проследить в отношении запроса @Aaronaught для уточнения, пул будет использоваться для запросов балансировки нагрузки к внешней службе. Чтобы поместить это в сценарий, который, вероятно, будет легче сразу понять, в отличие от моей прямой ситуации. У меня есть объект сеанса, который функционирует аналогично объекту ISession из NHibernate. То, что каждый уникальный сеанс управляет подключением к базе данных. В настоящее время у меня есть 1 длительный объект сеанса, и я сталкиваюсь с проблемами, из-за которых мой поставщик услуг ограничивает скорость использования этого отдельного сеанса.

Из-за того, что они не ожидали, что один сеанс будет рассматриваться как долгосрочная служебная учетная запись, они, по-видимому, рассматривают его как клиента, который использует свой сервис. Это подводит меня к моему вопросу: вместо одного отдельного сеанса я бы создал пул из разных сеансов и разделил запросы к службе по нескольким сеансам, вместо того чтобы создать единую координационную точку, как я это делал ранее.

Надеюсь, что фон предлагает некоторую ценность, но прямо ответить на некоторые из ваших вопросов:

Q: Дорогие объекты для создания?
A: Ни один объект не является пулом ограниченных ресурсов

В: Будут ли они приобретаться / выпускаться очень часто?
A: Да, еще раз можно подумать о сеансах NHibernate IS, где 1 обычно получается и освобождается в течение каждого запроса одной страницы.

Q: Достаточно ли будет простого «первым пришел - первым обслужен», или вам нужно что-то более умное, то есть, чтобы предотвратить голод?
A: Достаточно простого распределения типа циклического перебора, под голодом я предполагаю, что вы имеете в виду, если нет доступных сеансов, когда вызывающие абоненты блокируются в ожидании выпусков. Это на самом деле не применимо, поскольку сеансы могут совместно использоваться разными абонентами. Моя цель - распределить использование по нескольким сеансам, а не по одному сеансу.

Я полагаю, что это, вероятно, отклонение от обычного использования пула объектов, поэтому я изначально исключил эту часть и планировал просто адаптировать шаблон, чтобы разрешить совместное использование объектов, а не позволять ситуации голода когда-либо происходить.

Q: Как насчет таких вещей, как приоритеты, ленивая или энергичная загрузка и т. Д .?
A: Приоритеты не требуются, для простоты просто предположите, что я создам пул доступных объектов при создании самого пула.

Ответы [ 9 ]

304 голосов
/ 04 апреля 2010

Этот вопрос немного сложнее, чем можно было бы ожидать из-за нескольких неизвестных: поведение пулируемого ресурса, ожидаемое / требуемое время жизни объектов, реальная причина, по которой требуется пул, и т. Д. Обычно пулы являются особыми цель - пулы потоков, пулы соединений и т. д. - потому что легче оптимизировать один, когда вы точно знаете, что ресурс делает, и, что более важно, имеете контроль над тем, как этот ресурс реализован.

Поскольку это не так просто, я попытался предложить довольно гибкий подход, с которым вы можете поэкспериментировать и посмотреть, что работает лучше всего. Заранее извиняюсь за длинный пост, но есть много возможностей для реализации, когда дело доходит до реализации достойного пула ресурсов общего назначения. и я действительно только царапаю поверхность.

Пул общего назначения должен иметь несколько основных «настроек», в том числе:

  • Стратегия загрузки ресурсов - нетерпеливый или ленивый;
  • Загрузка ресурсов механизм - как его создать;
  • Стратегия доступа - вы упоминаете «круговой прием», который не так прост, как кажется; эта реализация может использовать циклический буфер, который аналогичен , но не идеален, поскольку пул не контролирует, когда ресурсы фактически возвращаются. Другие варианты - FIFO и LIFO; У FIFO будет больше шаблонов произвольного доступа, но LIFO значительно упрощает реализацию стратегии освобождения с наименьшим количеством использований (о которой вы говорили, что она выходит за рамки, но все же стоит упомянуть).

Для механизма загрузки ресурсов .NET уже дает нам чистую абстракцию - делегаты.

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

Пропустите это через конструктор пула, и мы закончили с этим. Использование универсального типа с ограничением new() также работает, но это более гибко.


Из двух других параметров стратегия доступа является более сложным, поэтому мой подход заключался в использовании подхода на основе наследования (интерфейса):

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

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

Концепция здесь проста - мы позволим общедоступному классу Pool обрабатывать общие проблемы, такие как безопасность потоков, но будем использовать разные «хранилища элементов» для каждого шаблона доступа. LIFO легко представляется стеком, FIFO является очередью, и я использовал не очень оптимизированную, но, вероятно, адекватную реализацию циклического буфера, используя List<T> и указатель индекса, чтобы приблизить схему циклического доступа.

Все перечисленные ниже классы являются внутренними классами Pool<T> - это был выбор стиля, но поскольку они действительно не предназначены для использования вне Pool, это имеет смысл.

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

Это очевидные из них - стек и очередь. Я не думаю, что они действительно заслуживают большого объяснения. Круговой буфер немного сложнее:

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

Я мог бы выбрать несколько разных подходов, но суть в том, что ресурсы должны быть доступны в том же порядке, в котором они были созданы, что означает, что мы должны поддерживать ссылки на них, но помечать их как «используемые» (или нет). В худшем случае доступен только один слот, и для каждой выборки требуется полная итерация буфера. Это плохо, если у вас есть сотни объединенных ресурсов и вы получаете и выпускаете их несколько раз в секунду; на самом деле не проблема для пула из 5-10 предметов, а в типичном случае , когда ресурсы используются слабо, нужно продвинуть только один или два слота.

Помните, что эти классы являются частными внутренними классами - поэтому им не нужно много проверять ошибки, сам пул ограничивает доступ к ним.

Добавьте перечисление и фабричный метод, и мы закончили с этой частью:

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

Следующая проблема, которую нужно решить, это загрузка стратегии. Я определил три типа:

public enum LoadingMode { Eager, Lazy, LazyExpanding };

Первые два должны быть самоочевидными; третье - это своего рода гибрид, оно лениво загружает ресурсы, но фактически не начинает повторно использовать какие-либо ресурсы, пока пул не заполнится. Это было бы хорошим компромиссом, если вы хотите, чтобы пул был заполнен (что звучит так, как вы), но хотите отложить расходы на их фактическое создание до первого доступа (т. Е. Для улучшения времени запуска).

Методы загрузки на самом деле не слишком сложны, теперь у нас есть абстракция хранилища предметов:

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

Приведенные выше поля size и count относятся к максимальному размеру пула и общему количеству ресурсов, которыми владеет пул (но не обязательно доступно ), соответственно. AcquireEager является самым простым, предполагается, что элемент уже находится в магазине - эти элементы будут предварительно загружены при создании, то есть в методе PreloadItems, показанном последним.

AcquireLazy проверяет наличие свободных элементов в пуле, а если нет, то создает новый. AcquireLazyExpanding создаст новый ресурс, если пул еще не достиг целевого размера. Я пытался оптимизировать это, чтобы минимизировать блокировку, и я надеюсь, что не допустил ошибок (я проверил это в многопоточных условиях, но, очевидно, не исчерпывающе).

Вам может быть интересно, почему ни один из этих методов не удосуживается проверить, достиг ли магазин максимального размера. Я вернусь к этому через мгновение.


Теперь для самого бассейна. Вот полный набор личных данных, некоторые из которых уже были показаны:

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

Отвечая на вопрос, который я затмил в последнем абзаце - как обеспечить ограничение общего количества созданных ресурсов - оказывается, что в .NET уже есть совершенно хороший инструмент для этого, он называется Семафор и он разработан специально для того, чтобы разрешить фиксированному количеству потоков доступ к ресурсу (в этом случае «ресурс» является внутренним хранилищем элементов). Поскольку мы не внедряем полную очередь производителей / потребителей, это вполне соответствует нашим потребностям.

Конструктор выглядит так:

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

Здесь не должно быть сюрпризов. Единственное, на что следует обратить внимание - это специальный корпус для быстрой загрузки с использованием метода PreloadItems, уже показанного ранее.

Поскольку к настоящему моменту практически все абстрагировано, реальные методы Acquire и Release действительно очень просты:

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

Как объяснено ранее, мы используем Semaphore для управления параллелизмом вместо религиозной проверки состояния хранилища элементов. Пока приобретенные предметы правильно выпущены, беспокоиться не о чем.

Не в последнюю очередь, есть очистка:

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

Цель этого свойства IsDisposed станет ясна через мгновение. Все, что на самом деле делает основной метод Dispose, - это удаляет фактически объединенные элементы, если они реализуют IDisposable.


Теперь вы можете в основном использовать это как есть, с блоком try-finally, но мне не нравится этот синтаксис, потому что если вы начнете передавать объединенные ресурсы между классами и методами, это будет очень запутанным. Возможно, что у основного класса, который использует ресурс, даже нет ссылки на пул. Это действительно становится довольно грязным, поэтому лучше создать «умный» объект из пула.

Допустим, мы начнем со следующего простого интерфейса / класса:

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

Вот наш притворный одноразовый ресурс Foo, который реализует IFoo и имеет некоторый шаблонный код для генерации уникальных идентификаторов. Что мы делаем, так это создаем еще один особый объект из пула:

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

Это просто передает все "настоящие" методы к его внутреннему IFoo (мы могли бы сделать это с помощью библиотеки динамического прокси, такой как Castle, но я не буду вдаваться в подробности). Он также поддерживает ссылку на Pool, которая его создает, поэтому, когда мы Dispose этот объект, он автоматически выпускает себя обратно в пул. За исключением , когда пул уже был удален - это означает, что мы находимся в режиме "очистки", и в этом случае он фактически вместо этого очищает внутренний ресурс .


Используя описанный выше подход, мы получаем код, подобный следующему:

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

Это очень хорошая вещь, которую можно сделать. Это означает, что код, который использует IFoo (в отличие от кода, который его создает), на самом деле не должен знать о пуле. Вы даже можете внедрять IFoo объекты, используя вашу любимую библиотеку DI и Pool<T> в качестве поставщика / фабрики.


Я поместил полный код в PasteBin для вашего удобства копирования и вставки. Есть также короткая тестовая программа , которую вы можете использовать для работы с различными режимами загрузки / доступа и многопоточными условиями, чтобы убедиться, что она поточно-безопасна и не глючит.

Дайте мне знать, если у вас есть какие-либо вопросы или сомнения по этому поводу.

50 голосов
/ 05 июня 2015

Объединение объектов в .NET Core

В ядре dotnet добавлена ​​реализация пула объектов, добавленная в библиотеку базовых классов (BCL). Вы можете прочитать исходный выпуск GitHub здесь и просмотреть код для System.Buffers . В настоящее время ArrayPool является единственным доступным типом и используется для объединения массивов. Здесь есть хороший пост .

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

Пример его использования можно увидеть в ASP.NET Core. Поскольку ASP.NET Core находится в ядре dotnet BCL, он может совместно использовать свой пул объектов с другими объектами, такими как сериализатор JSON Newtonsoft.Json. Вы можете прочитать это сообщение в блоге для получения дополнительной информации о том, как Newtonsoft.Json делает это.

Объединение объектов в компиляторе Microsoft Roslyn C #

Новый компилятор Microsoft Roslyn C # содержит тип ObjectPool , который используется для объединения часто используемых объектов, которые обычно бывают обновлены и очень часто собирают мусор. Это уменьшает количество и размер операций по сбору мусора, которые должны произойти. Существует несколько различных вложенных реализаций, использующих ObjectPool (см .: Почему в Roslyn так много реализаций Object Pooling? ).

1 - SharedPools - Хранит пул из 20 или 100 объектов, если используется BigDefault.

// 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 и StringBuilderPool - Не строго отдельные реализации, а обертки вокруг реализации SharedPools, показанной выше специально для List и StringBuilder. Так что это повторно использует пул объектов, хранящихся в 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 и PooledHashSet - Они используют ObjectPool напрямую и имеют совершенно отдельный пул объектов. Хранит бассейн из 128 предметов.

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

Эта библиотека предоставляет пул для MemoryStream объектов. Это замена для 1049 *. У него точно такая же семантика. Он был разработан инженерами Bing. Прочтите сообщение в блоге здесь или посмотрите код на 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); 
}

Обратите внимание, что RecyclableMemoryStreamManager должен быть объявлен один раз, и он будет действовать для всего процесса - это пул. Можно использовать несколько пулов, если хотите.

7 голосов
/ 02 апреля 2010

Нечто подобное может удовлетворить ваши потребности.

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

                }
            }
        }
    }
}

Пример использования

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);
        }
    }
}
5 голосов
/ 18 мая 2015
4 голосов
/ 24 августа 2010

Мне очень нравится реализация Аронота, особенно потому, что он обрабатывает ожидающий ресурс, который становится доступным благодаря использованию семафора. Я бы хотел сделать несколько дополнений:

  1. Измените sync.WaitOne() на sync.WaitOne(timeout) и выставьте тайм-аут в качестве параметра для метода Acquire(int timeout). Это также потребовало бы обработки условия, когда поток прерывал ожидание, когда объект стал доступен.
  2. Добавить Recycle(T item) метод для обработки ситуаций, когда объект должен быть переработан в случае сбоя, например.
4 голосов
/ 02 апреля 2010

Раньше Microsoft предоставила инфраструктуру через Microsoft Transaction Server (MTS) и более позднюю версию COM + для создания пула объектов для COM-объектов. Эта функциональность была перенесена в System.EnterpriseServices в .NET Framework, а теперь и в Windows Communication Foundation.

Объединение объектов в WCF

Эта статья написана на .NET 1.1, но все еще должна применяться в текущих версиях Framework (даже если WCF является предпочтительным методом).

Объединение объектов .NET

3 голосов
/ 03 апреля 2013

Это еще одна реализация с ограниченным количеством объектов в пуле.

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 голосов
/ 29 марта 2010

Java ориентирован, эта статья представляет шаблон пула connectionImpl и шаблон абстрагированного объекта и может быть хорошим первым подходом: http://www.developer.com/design/article.php/626171/Pattern-Summaries-Object-Pool.htm

Пул объектов Pattern:

pattern

0 голосов
/ 30 июня 2015

Расширение msdn для создания пула объектов с помощью ConcurrentBag.

https://github.com/chivandikwa/ObjectPool

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...