C#オブジェクトプーリングパターンの実装


165

Sql接続プーリングの範囲内で、限られたリソースに対して共有オブジェクトプール戦略を実装するための優れたリソースを持っている人はいますか?(つまり、スレッドセーフであることを完全に実装します)。

明確化のために@Aaronaughtリクエストに関してフォローアップすることは、プールの使用が外部サービスへのリクエストのロードバランシングのためになることです。私の直接的な状況とは対照的に、おそらくすぐに理解しやすいシナリオにそれを置くこと。同様に機能するセッションオブジェクトがあります。ISessionNHibernateオブジェクトます。一意の各セッションがデータベースへの接続を管理すること。現在、1つの長時間実行セッションオブジェクトがあり、サービスプロバイダーがこの個別セッションの使用をレート制限しているという問題が発生しています。

単一のセッションが長時間実行されるサービスアカウントとして扱われるという期待の欠如のため、彼らは明らかに、サービスをハンマーしているクライアントとしてそれを扱います。ここで私の質問に移動します。個別のセッションを1つ持つ代わりに、以前のように単一のフォーカルポイントを作成するのではなく、異なるセッションのプールを作成し、複数のセッションにわたってサービスにリクエストを分割します。

うまくいけば、その背景にはいくつかの価値がありますが、いくつかの質問に直接答えることができます:

Q:オブジェクトの作成には費用がかかりますか?
A:限られたリソースのプールであるオブジェクトはありません

Q:非常に頻繁に取得/リリースされますか?
A:はい。これもまた、NHibernate ISessionsと考えることができます。この場合、通常、1つのページ要求ごとに1が取得および解放されます。

Q:単純な先着順で十分でしょうか、またはよりインテリジェントなもの、つまり飢餓を防ぐものが必要ですか?
A:単純なラウンドロビンタイプの配布で十分です。飢餓により、発信者が解放を待ってブロックされる使用可能なセッションがない場合を想定しています。セッションは異なる呼び出し元で共有できるため、これは実際には適用できません。私の目標は、単一のセッションではなく、複数のセッションに使用量を分散することです。

これはおそらく、オブジェクトプールの通常の使用法との相違であると考えられます。そのため、もともとこの部分を省略し、パターンを調整してオブジェクトの共有を可能にすることを計画しました。

Q:優先度、遅延読み込みと積極読み込みなどはどうなりますか?
A:優先順位付けは含まれていません。簡単にするために、プール自体の作成時に使用可能なオブジェクトのプールを作成すると仮定します。


1
あなたの要件について少し教えていただけますか?すべてのプールが同じように作成されるわけではありません。オブジェクトを作成するには費用がかかりますか?それらは非常に頻繁に取得/リリースされますか?単純な先着順で十分でしょうか、またはよりインテリジェントなもの、つまり飢餓を防ぐものが必要ですか?優先度、遅延読み込みと積極読み込みなどはどうですか?追加できるものはすべて、私たち(または少なくとも私)がより完全な答えを出すのに役立ちます。
アーロンノート2010

クリス-2番目と3番目の段落を見て、これらのセッションを本当に無期限に存続させるべきかどうか疑問に思いますか?それはあなたのサービスプロバイダーが嫌いなものだと思われます(長時間実行セッション)ので、必要に応じて新しいセッションを起動し、使用されていないとき(特定の期間の後)それらをシャットダウンするプール実装を探しているかもしれません。これは可能ですが、もう少し複雑なので確認します。
アーロノート

私のソリューションは単なる仮説であるため、ソリューションの堅牢性が必要かどうかはまだわかりません。私のサービスプロバイダーが私に嘘をついているだけで、そのサービスが売り切れて、ユーザーを非難する方法の言い訳を見つけただけの可能性があります。
Chris Marisic 2010

1
TPL DataFlow BufferBlockは、必要なほとんどのことを実行すると思います。
消費者、2014年

1
スレッド環境でのプーリングは繰り返し発生する問題であり、リソースプールやリソースキャッシュなどの設計パターンによって解決されます。詳細については、パターン指向ソフトウェアアーキテクチャ、第3巻:リソース管理のパターンをご覧ください。
Fuhrmanator 2014年

回答:


59

.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を使用するいくつかの異なるサブ実装があります(参照:Roslynにはオブジェクトプーリングの実装がそれほど多くあるのはなぜですか?)。

1- SharedPools -20オブジェクトのプール、またはBigDefaultが使用されている場合は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- 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オブジェクトのプールを提供します。これは、のドロップイン代替品です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一度宣言する必要があり、プロセス全体で有効になることに注意してください。これがプールです。必要に応じて、複数のプールを使用しても問題ありません。


2
これは素晴らしい答えです。C#6とVS2015がRTMになった後、Rosyln自体が使用するように調整されていれば、明らかにこれが最も良いので、これを受け入れられた回答にするでしょう。
Chris Marisic

同意しますが、どの実装を使用しますか?Roslynには3つが含まれています。回答の中で私の質問へのリンクを参照してください。
Muhammad Rehan Saeed 2015

1
それぞれが非常に明確に定義された目的を持っているようであり、オープンエンドのワンサイズの選択だけがすべての靴に合うよりもはるかに優れています。
Chris Marisic 2015年

1
@MuhammadRehanSaeedのArrayPoolによるすばらしい追加
Chris Marisic

1
これを見ると、RecyclableMemoryStream超高性能の最適化のための驚くべき追加となります。
Chris Marisic

315

この質問は、いくつかの未知数のため、予想よりも少しトリッキーです:プールされるリソースの動作、オブジェクトの予想される/必要なライフタイム、プールが必要とされる実際の理由など。通常、プールは特別な目的-スレッドですプール、接続プールなど-リソースの動作を正確に理解している場合に最適化する方が簡単であり、さらに重要なことに、そのリソースの実装方法を制御できるためです。

それはそれほど単純ではないので、私が試みたのは、かなり柔軟なアプローチを提供して、実験して最も効果的なものを確認できるようにすることです。 長い投稿については事前にお詫びしますが、まともな汎用リソースプールの実装に関しては、対処すべき多くの根拠があります。ほんの表面を引っ掻いているだけです。

汎用プールには、次のようないくつかの主要な「設定」が必要です。

  • リソース読み込み戦略-熱心または遅延。
  • リソース読み込みメカニズム -実際にそれを構築する方法。
  • アクセス戦略-「ラウンドロビン」と言っているが、これは思ったほど簡単ではない。この実装は、リソースが実際に再利用されるタイミングをプールが制御できないため、ていますが完全ではない循環バッファーを使用できます。他のオプションはFIFOとLIFOです。FIFOにはランダムアクセスパターンが多く含まれますが、LIFOを使用すると、Least-Recently-Used解放戦略の実装が大幅に容易になります(これは範囲外であると言いましたが、言及する価値はあります)。

リソースのロードメカニズムについては、.NETは既に明確な抽象化を提供しています-デリゲート。

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

これをプールのコンストラクターに渡すと、これでほぼ完了です。new()制約付きのジェネリック型を使用することもできますが、これはより柔軟です。


他の2つのパラメーターのうち、アクセス戦略はより複雑な獣であるため、私のアプローチは継承(インターフェース)ベースのアプローチを使用することでした:

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です。スレッドの安全性などの一般的な問題はpublic クラスで処理しますが、アクセスパターンごとに異なる「アイテムストア」を使用します。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; }
        }
    }

いくつかの異なるアプローチを選択できたかもしれませんが、肝心なことは、リソースは作成されたのと同じ順序でアクセスする必要があるということです。 )。最悪のシナリオでは、利用可能なスロットは1つだけであり、フェッチごとにバッファーの完全な反復が必要です。何百ものリソースがプールされていて、それらを1秒あたり数回取得および解放している場合、これは悪いことです。5〜10アイテムのプールでは実際には問題ではなく、リソースが軽く使用される一般的なケースでは、1つまたは2つのスロットを進めるだけで済みます。

これらのクラスはプライベートな内部クラスであることを忘れないでください。そのため、多くのエラーチェックを必要とせず、プール自体がそれらへのアクセスを制限しています。

列挙とファクトリーメソッドをスローすると、この部分は完了です。

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

解決する次の問題は、ローディング戦略です。私は3つのタイプを定義しました:

public enum LoadingMode { Eager, Lazy, LazyExpanding };

最初の2つは自明です。3番目は一種のハイブリッドです。リソースを遅延ロードしますが、プールがいっぱいになるまで実際にはリソースの再利用を開始しません。これは、プールを一杯にしたい(そうするように聞こえる)が、最初にアクセスするまで(つまり、起動時間を改善するために)実際にプールを作成する費用を延期したい場合に、適切なトレードオフになります。

ローディングメソッドはそれほど複雑ではありませんが、アイテムストアの抽象化があります:

    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にはすでにそのための完全に優れたツールがあり、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すでに前に示し方法。

ほとんどすべてが今ではきれいに抽象化されているので、実際の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、一意のIDを生成するための定型コードを実装して持つ、使い捨ての偽装リソースです。私たちがすることは、別の特別なプールされたオブジェクトを作成することです:

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ライブラリを使用して、プロバイダー/ファクトリーとしてオブジェクトを注入 することもできIFooますPool<T>


私が置かれているペーストビン上の完全なコードをコピー&ペーストの楽しみのために。また、さまざまなロード/アクセスモードやマルチスレッド条件をいじって、スレッドセーフでバグがないことを確認するために使用できる短いテストプログラムもあります。

これについて質問や懸念がある場合はお知らせください。


62
私がSOで読んだ最も完全で有用な興味深い答えの1つ。
Josh Smeaton

オブジェクトを解放することは常に非常に漏れやすい方法で処理されているようであり、あなたが示したような構成は、私が座っていないことを示し、それを構築しようとしたところ、あなたの答えが私の問題を解決するために必要なすべての情報を与えてくれました。私は特定の状況では、スレッド間でインスタンスを共有でき、それらをプールに解放する必要がないので、これをかなり単純化できると思います。
Chris Marisic

ただし、単純なアプローチが最初に機能しない場合、私のケースのリリースをインテリジェントに処理する方法について、いくつかのアイデアが頭に浮かびます。具体的には、セッション自体に障害が発生したことを特定し、それを破棄して新しいセッションをプールに置き換えることができるように、リリースを確立すると思います。この時点でのこの投稿がC#3.0でのオブジェクトプーリングの最も決定的なガイドであるかどうかに関係なく、他の誰かがこれについてさらにコメントがあるかどうかを楽しみにしています。
Chris Marisic

@Chris:WCFクライアントプロキシについて話している場合は、そのパターンもありますが、依存関係インジェクターまたはメソッドインターセプターを使用して効果的に使用する必要があります。DIバージョンは、カスタムプロバイダーのカーネルを使用して、障害が発生した場合の新しいバージョンを取得します。メソッドインターセプトバージョン(私の設定)は、既存のプロキシをラップし、それぞれの前に障害チェックを挿入します。これをこのようなプールに統合するのがどれほど簡単かはわかりません(これを書いたばかりなので、実際には試していません!)。
アーロノート

5
非常に印象的ですが、ほとんどの状況では少し過剰に設計されています。このようなものがフレームワークの一部になることを期待しています。
ChaosPandion 2010

7

このようなものがあなたのニーズに合うかもしれません。

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

1
以前のコメントをスクラッチ。このプールにはしきい値がないようで、おそらく必要ないかもしれませんが、要件によって異なります。
アーロンノート2010

1
@Aaronaught-それは本当に奇妙ですか?必要な機能だけを提供する軽量プールを作成したかったのです。クラスを適切に使用するのはクライアントの責任です。
ChaosPandion

1
+1を使用すると、バッキングタイプをList / HashTableなどに変更し、カウンターをロールオーバーするように変更するだけで、目的に合わせて調整できます。ランダムな質問では、プールオブジェクト自体の管理をどのように処理しますか?そこにシングルトンであると定義するIOCコンテナーに入れますか?
Chris Marisic 2010

1
静的な読み取り専用にする必要がありますか?しかし、例外がある場合は、finallyステートメント内にputを配置するのは奇妙だと思います。例外として、オブジェクト自体に障害がある可能性はありませんか?Putメソッド内でそれを処理し、オブジェクトに障害があるかどうかを確認するためのいくつかのタイプのチェックを省略し、前のオブジェクトを挿入する代わりにプールに追加する新しいインスタンスを作成するために、それを省略しますか?
Chris Marisic

1
@Chris-私は過去に便利だと思ったシンプルなツールを提供しています。後は君しだい。必要に応じてコードを変更して使用します。
ChaosPandion 2010

6

そのリンクをありがとう。この実装にはサイズの制限がないため、オブジェクトの作成にスパイクがある場合、それらのインスタンスは収集されず、別のスパイクが発生するまでおそらく使用されません。ただし、これは非常にシンプルで理解しやすく、最大サイズの制限を追加するのは難しくありません。
Muhammad Rehan Saeed 2015


4

当時、マイクロソフトは、Microsoft Transaction Server(MTS)以降のCOM +を通じて、COMオブジェクトのオブジェクトプーリングを行うためのフレームワークを提供していました。この機能は、.NET FrameworkのSystem.EnterpriseServicesに引き継がれ、現在はWindows Communication Foundationに引き継がれています。

WCFでのオブジェクトプーリング

この記事は.NET 1.1からのものですが、フレームワークの現在のバージョンにも適用されます(WCFが推奨される方法ですが)。

オブジェクトプーリング.NET


+1はIInstanceProvider、ソリューションにこれを実装するため、インターフェイスが存在することを示します。私は常に、Microsoft提供のインターフェースが適切な定義を提供するときに、その背後に自分のコードを積み重ねるのが好きです。
Chris Marisic 2010

4

私は本当にAronaughtの実装が好きです-特に彼はセマフォを使用して利用可能になるまで待機しているリソースを処理するためです。追加したいことがいくつかあります。

  1. タイムアウトに変更sync.WaitOne()sync.WaitOne(timeout)Acquire(int timeout)メソッドのパラメーターとして公開します。これは、スレッドがオブジェクトが使用可能になるのを待機しているときにタイムアウトが発生したときに条件を処理することも必要になります。
  2. Recycle(T item)たとえば、障害が発生したときにオブジェクトをリサイクルする必要がある状況を処理するメソッドを追加します。

3

これは別の実装であり、プール内のオブジェクトの数は限られています。

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

Java指向のこの記事は、connectionImplプールパターンと抽象化されたオブジェクトプールパターンを公開しているので、最初のアプローチとしてはよいでしょう 。 htm

オブジェクトプールパターン:

パターン



弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.