C#でオブジェクトプールデザインパターンを使用する方法

アプリケーションを構築するとき、作成するのに非常に費用がかかるオブジェクトに出くわすことがよくあります。一部のシナリオでは、新しいオブジェクトを作成するコストが、アプリケーションのパフォーマンスに影響を与えるほど高くなります。ここで、オブジェクトプールのデザインパターンが役に立ちます。 

オブジェクトプールデザインパターンは、アプリケーションがオブジェクトを必要とするたびにオブジェクトを再作成するのではなく、オブジェクトをリサイクルするために使用される作成デザインパターンです。オブジェクトの再利用可能なインスタンスをリソースプールに保持し、必要に応じてそれらを実行することにより、このパターンは、オブジェクトの初期化、インスタンス化、および破棄のオーバーヘッドを最小限に抑え、アプリケーションのパフォーマンスを向上させるのに役立ちます。

アプリケーションがオブジェクトを要求し、そのオブジェクトがプールから利用可能になると、そのオブジェクトはプールから返されます。要求されたタイプのオブジェクトがプールから利用できない場合、オブジェクトの新しいインスタンスが作成されて返されます。アプリケーションがオブジェクトを必要としなくなると、オブジェクトはプールに送り返されます。

オブジェクトプールが保持できるオブジェクトの最小数と最大数は構成可能です。アプリケーションがプールからのオブジェクトを必要としているが、最大数のオブジェクトが割り当てられている場合、一般的なカスタムオブジェクトプールの実装では、次の戦略の1つ以上を採用できます。

  1. nullを返すか、例外をスローします
  2. オブジェクトが利用可能になるまで通話をブロックする
  3. より多くのオブジェクトに対応するためにプールサイズを増やします

オブジェクトプールは、データベース接続プールに似ています。接続プールがデータベースへの接続の最大数を制御するのと同様に、オブジェクトプールはアプリケーションが使用するクラスインスタンスの数を制御します。

C#でジェネリックオブジェクトプールを作成する

基本がわかったので、実装に飛び込みましょう。オブジェクトプールのデザインパターンを実装するときは、再利用性、単純さ、構成可能性、さらにはスレッドセーフなどの要素も考慮する必要があります。

この例では、ConcurrentBag クラスを利用してオブジェクトを格納します。名前空間ConcurrentBag内のクラスSystem.Collections.Concurrentは、ロックフリーでスレッドセーフな順序付けられていない要素のコレクションを提供することに注意してください。また、aとの間のオブジェクトの挿入と削除ConcurrentBagは非常に高速であることに注意してください。特に、同じスレッドがコレクションからアイテムを同時に挿入および削除しようとしている場合はそうです。

これがカスタムObjectPoolクラスの構造です。ConcurrentBagオブジェクトを格納するためのインスタンスの使用法に注意してください。

パブリッククラスObjectPoolここで、T:new()

    {{

        プライベート読み取り専用ConcurrentBagitems = new ConcurrentBag();

        プライベートintカウンター= 0;

        private int MAX = 10;

        public void Release(T item)

        {{

            //やること          

        }

        public T Get()

        {{

           //やること

        }

    }

次のコードスニペットは、Getメソッドの実装を示しています。Get一方が利用可能である場合、メソッドは、オブジェクトプールからインスタンスを返します。使用可能なものがない場合は、新しいオブジェクトが作成されて返されます。これらのシナリオの両方で、counter変数は必要に応じて増減されます。並行コレクションを使用しているため、つまりConcurrentBagこの例では、並行性が処理されることに注意してください。

public T Get()

        {{

            Tアイテム;

            if(items.TryTake(out item))

            {{

                カウンター-;

                返却物;

            }

            そうしないと

            {{

                T obj = new T();

                items.Add(obj);

                カウンター++;

                objを返します。

            }

        }

MAX整数変数は、ここでは、ハードコード化されていますが、それは、構成することができます。このクラスは封印も静的でもないので、好きなように拡張できます。

このReleaseメソッドは、不要になったオブジェクトをオブジェクトプールに解放するために使用されます。counter変数の値が変数の値よりも小さいかどうかをチェックし、小さい場合MAXは、渡されたオブジェクトをパラメーターとしてコレクションに追加します。

public void Release(T item)

        {{

            if(カウンター<MAX)

            {{

                items.Add(item);

                カウンター++;

            }           

        }

というクラスを作成したと仮定しMyClassて、ObjectPoolクラスを使用してオブジェクトプールに追加する方法を次に示します。

static void Main(string [] args)

        {{

            ObjectPool objPool = new ObjectPool();

            MyClass obj = objPool.Get();

            objPool.Release(obj);

            Console.Read();

        }

このカスタムオブジェクトプールの実装を変更して、プールの最小サイズと最大サイズを構成ファイルから読み取れるようにすることができます。オブジェクトプールの初期化の一部として、プールに最小数のオブジェクトが含まれていることを確認することもできます。

オブジェクトプールは、作成または管理にコストがかかるクラスの複数のインスタンスが必要な場合に、リソースのオーバーヘッドを削減するのに役立ちます。アプリケーションで同じクラスを何度もインスタンス化する必要がある場合は、このデザインパターンを使用して、最適なパフォーマンスを確保してください。