[C#] C # 개체 풀링 패턴 구현

누구나 SQL 연결 풀링을 통해 제한된 리소스에 대한 공유 객체 풀 전략을 구현하는 데 유용한 리소스가 있습니까? (즉, 스레드로부터 안전하도록 완전히 구현되어야 함).

설명을위한 @Aaronaught 요청과 관련하여 후속 조치를 수행하려면 풀 사용은 외부 서비스에 대한 요청을로드 밸런싱하는 것입니다. 내가 직접 배치하는 것과는 반대로 즉시 이해하기 쉬운 시나리오에이를 수 있습니다. ISessionNHibernate 의 객체 와 유사하게 작동하는 세션 객체가 있습니다 . 각 고유 세션은 데이터베이스에 대한 연결을 관리합니다. 현재 1 개의 장시간 실행되는 세션 객체가 있으며 서비스 제공 업체 가이 개별 세션의 사용을 제한하는 문제가 발생합니다.

단일 세션이 장기 실행 서비스 계정으로 취급 될 것이라는 기대치가 부족하여 서비스를 망치는 클라이언트로 간주합니다. 여기에 내 질문에 하나의 개별 세션이있는 대신 다른 세션 풀을 만들고 이전에했던 것처럼 단일 초점을 만드는 대신 여러 세션에서 서비스로 요청을 분할합니다.

그 배경이 가치를 제공하지만 일부 질문에 직접 대답하기를 바랍니다.

Q : 개체를 만드는 데 비용이 많이 듭니까?
A : 제한된 리소스 풀이있는 개체가 없습니다 .

Q : 매우 자주 획득 / 출시됩니까?
A : 그렇습니다. 다시 한번 그들은 매 페이지 요청이있을 때마다 1을 획득하고 공개하는 NHibernate ISessions를 생각할 수 있습니다.

Q : 간단한 선착순으로 충분합니까, 아니면 기아를 막을 수있는보다 지능적인 것이 필요합니까?
A : 단순한 라운드 로빈 유형 배포로 충분할 것입니다. 기아로 인해 발신자가 릴리스를 기다리는 동안 사용 가능한 세션이없는 경우를 의미한다고 가정합니다. 다른 발신자가 세션을 공유 할 수 있으므로 실제로 적용 할 수 없습니다. 내 목표는 단일 세션이 아닌 여러 세션에 사용량을 분산시키는 것입니다.

나는 이것이 아마도 객체 풀을 정상적으로 사용하는 것과의 차이라고 생각합니다. 그래서 원래이 부분을 생략하고 기아 상황이 발생하는 것과 반대로 객체 공유를 허용하도록 패턴을 조정하려고 계획 한 이유입니다.

Q : 우선 순위, 게으른 대 열렬한로드 등과 같은 것은 어떻습니까?
A : 우선 순위는 없습니다. 단순화를 위해 풀 자체를 만들 때 사용 가능한 개체 풀을 만들겠다고 가정하기 만하면됩니다.



답변

.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에서 확인할 수 있습니다. 닷넷 코어 BCL에 있기 때문에 ASP.NET 코어는 Newtonsoft.Json의 JSON 시리얼 라이저와 같은 다른 객체와 객체 풀을 공유 할 수 있습니다. 당신이 읽을 수있는 Newtonsoft.Json이 일을하는 방법에 대한 자세한 내용은 블로그 게시물을.

Microsoft Roslyn C # 컴파일러의 개체 풀링

새로운 Microsoft Roslyn C # 컴파일러에는 ObjectPool 유형이 포함되어 있습니다. ObjectPool 유형은 일반적으로 자주 사용되며 가비지 수집이 자주 발생하는 자주 사용하는 객체를 풀링하는 데 사용됩니다. 이렇게하면 발생해야하는 가비지 수집 작업의 양과 크기가 줄어 듭니다. ObjectPool을 사용하는 몇 가지 다른 하위 구현 이 있습니다 (Roslyn에 Object Pooling 구현이 많은 이유는 무엇입니까? 참조). ).

1- SharedPools -BigDefault가 사용되는 경우 20 개 오브젝트 또는 100 개 풀을 저장합니다.

// 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- ListPoolStringBuilderPool- 구현을 엄격하게 분리하지는 않지만 List 및 StringBuilder에 대해 위에 표시된 SharedPools 구현 주위의 래퍼입니다. 따라서 이것은 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- PooledDictionaryPooledHashSet -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객체 풀링을 제공 합니다. 의 대체품입니다 System.IO.MemoryStream. 그것은 정확히 같은 의미론을 가지고 있습니다. 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가 전체 살 것이고, 한 번 선언해야합니다 과정은-이 풀이다. 원하는 경우 여러 풀을 사용하는 것이 좋습니다.


답변

풀링되는 자원의 동작, 개체의 예상 / 필수 수명, 풀이 필요한 실제 이유 등 일반적으로 풀은 특수 목적의 스레드입니다. 풀, 연결 풀 등-리소스의 기능을 정확히 알고 제어 할 수있는 리소스를 정확히 파악할 때 최적화하기가 더 쉽기 때문에 리소스의 구현 방법을 하는 .

그렇게 간단하지 않기 때문에 내가 시도한 것은 실험하고 가장 잘 작동하는 것을 볼 수있는 상당히 유연한 접근 방식을 제공하는 것입니다. 긴 게시물에 대해 사전에 사과하지만 적절한 범용 리소스 풀을 구현할 때 다루어야 할 근거가 많이 있습니다. 그리고 난 정말 표면을 긁적입니다.

범용 풀에는 다음을 포함하여 몇 가지 주요 “설정”이 있어야합니다.

  • 자원 로딩 전략-간절하거나 게으른;
  • 자원 로딩 메커니즘 -실제로 구성하는 방법;
  • 접근 전략-당신은 들리는 것처럼 간단하지 않은 “라운드 로빈”을 언급합니다. 이 구현은 풀이 리소스가 실제로 재생되는시기를 제어 할 수 없으므로 유사 하지만 완벽하지 않은 순환 버퍼를 사용할 수 있습니다 . 다른 옵션은 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>라운드 로빈 액세스 패턴을 근사하기 위해 and 및 포인터를 사용하여 최적화되지는 않았지만 아마도 적절한 원형 버퍼 구현을 사용했습니다 .

아래의 모든 클래스는 내부 클래스입니다. 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;
    }

위 의 sizecount필드는 풀의 최대 크기와 풀이 소유 한 총 리소스 수를 나타냅니다 (그러나 반드시 사용 가능한 것은 아님 ). 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에는 이미 완벽하게 좋은 도구가 있으며 Semaphore 라고합니다. 하며 고정을 허용하도록 특별히 설계되었습니다 자원에 대한 스레드 액세스 수 (이 경우 “자원”은 내부 항목 저장 소임) 우리는 완전한 생산자 / 소비자 대기열을 구현하지 않기 때문에 우리의 요구에 완벽하게 적합합니다.

생성자는 다음과 같습니다.

    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 이미 앞에서 설명한 방법을 .

지금까지 거의 모든 것이 깨끗하게 요약되었으므로 실제 방법 AcquireRelease방법은 매우 간단합니다.

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

다음 은 고유 한 ID를 생성하기위한 상용구 코드를 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코드는 실제로 풀을 인식 할 필요가 없습니다. 선호하는 DI 라이브러리와 공급자 / 공장을 사용하여 개체를 주입 할 수도 있습니다 .IFooPool<T>


나는 넣었습니다 페이스트 빈에 전체 코드를 하여 복사 및 붙여 넣기 즐거움을 위해. 스레드가 안전하고 버그가 없다는 것을 만족시키기 위해 다양한 로딩 / 액세스 모드와 멀티 스레드 조건을 가지고 놀 수 있는 간단한 테스트 프로그램도 있습니다.

이에 대해 궁금한 점이 있으면 알려주세요.


답변

이와 같은 것이 귀하의 요구에 적합 할 수 있습니다.

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


답변


답변

당시 Microsoft는 COM 개체에 대한 개체 풀링을 수행하기 위해 Microsoft Transaction Server (MTS) 이상 COM +를 통해 프레임 워크를 제공했습니다. 이 기능은 .NET Framework 및 현재 Windows Communication Foundation의 System.EnterpriseServices로 전달되었습니다.

WCF의 개체 풀링

이 문서는 .NET 1.1에서 작성되었지만 WCF가 선호되는 방법 임에도 불구하고 현재 버전의 Framework에 적용되어야합니다.

개체 풀링 .NET


답변

저는 Aronaught의 구현을 정말 좋아합니다. 특히 그는 세마포어를 사용하여 리소스를 사용할 수있게되기를 기다립니다. 내가 추가하고 싶은 몇 가지 추가 사항이 있습니다.

  1. 변경 sync.WaitOne()sync.WaitOne(timeout)와에 매개 변수로 타임 아웃을 노출Acquire(int timeout) 방법. 또한 스레드가 객체를 사용할 수 있기를 기다리는 시간이 초과 될 때 조건을 처리해야합니다.
  2. Recycle(T item)예를 들어, 장애가 발생했을 때 객체를 재활용해야하는 상황을 처리하는 메소드를 추가하십시오 .

답변

이것은 풀에 제한된 수의 개체가있는 또 다른 구현입니다.

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