次の方法で共有


初期化のインスタンス化

初期化のサンプルでは、インターフェイス IObjectControl を定義して「プール」のサンプルを拡張します。このインターフェイスでは、オブジェクトのアクティブ化と非アクティブ化を切り替えることで、その初期化をカスタマイズできます。 クライアントは、オブジェクトをプールに返すメソッドや、プールに返さないメソッドを呼び出します。

Note

このサンプルのセットアップ手順とビルド手順については、このトピックの最後を参照してください。

拡張ポイント

Windows Communication Foundation (WCF) 拡張を作成する最初の手順は、使用する拡張ポイントを決定することです。 WCF での用語 EndpointDispatcher は、受信メッセージをユーザーのサービス上のメソッド呼び出しに変換し、メソッドからの戻り値を送信メッセージに変換する実行時コンポーネントを表します。 WCF サービスでは、各エンドポイントの EndpointDispatcher を作成します。

EndpointDispatcher は EndpointDispatcher クラスを使用して、(サービスによって送受信されるすべてのメッセージの) エンドポイント スコープ拡張を提供します。 このクラスにより、EndpointDispatcher の動作を制御するさまざまなプロパティをカスタマイズできます。 このサンプルでは、サービス クラスのインスタンスを提供するオブジェクトをポイントする InstanceProvider プロパティに焦点を当てています。

IInstanceProvider

WCF では、EndpointDispatcher で IInstanceProvider インターフェイスを実装するインスタンス プロバイダーを使用してサービス クラスのインスタンスを作成します。 このインターフェイスに含まれるメソッドは、次の 2 つのみです。

  • GetInstance: メッセージが到着すると、このディスパッチャは GetInstance メソッドを呼び出し、メッセージを処理するためのサービス クラスのインスタンスを作成します。 このメソッドの呼び出し頻度は InstanceContextMode プロパティで決まります。 たとえば InstanceContextMode プロパティが InstanceContextMode.PerCall に設定されている場合、サービス クラスの新しいインスタンスが作成され、到着する各メッセージが処理されます。したがって、GetInstance はメッセージが到着するたびに呼び出されます。

  • ReleaseInstance: サービス インスタンスがメッセージの処理を完了すると、EndpointDispatcher は ReleaseInstance メソッドを呼び出します。 GetInstance メソッドと同様、このメソッドへの呼び出し頻度は InstanceContextMode プロパティで決まります。

オブジェクト プール

ObjectPoolInstanceProvider クラスには、オブジェクト プールの実装が含まれています。 このクラスは、サービス モデルのレイヤと対話する IInstanceProvider インターフェイスを実装しています。 EndpointDispatcher が、新しいインスタンスを作成する代わりに GetInstance メソッドを呼び出すと、カスタム実装はメモリ内プールで既存のオブジェクトを検索します。 検索されたオブジェクトが使用可能な場合は、そのオブジェクトが返されます。 使用可能なオブジェクトがない場合、ObjectPoolInstanceProviderActiveObjectsCount プロパティ (プールから返されるオブジェクト数) が最大プール サイズに達しているかどうかをチェックします。 最大サイズに達していない場合は、新しいインスタンスが作成されて呼び出し元に返され、その後 ActiveObjectsCount がインクリメントされます。 最大サイズに達していると、構成期間中、オブジェクト作成要求がキューに置かれます。 GetObjectFromThePool の実装を次のサンプル コードに示します。

private object GetObjectFromThePool()
{
    bool didNotTimeout =
       availableCount.WaitOne(creationTimeout, true);
    if(didNotTimeout)
    {
         object obj = null;
         lock (poolLock)
        {
             if (pool.Count != 0)
             {
                   obj = pool.Pop();
                   activeObjectsCount++;
             }
             else if (pool.Count == 0)
             {
                   if (activeObjectsCount < maxPoolSize)
                   {
                        obj = CreateNewPoolObject();
                        activeObjectsCount++;

                        #if (DEBUG)
                        WritePoolMessage(
                             ResourceHelper.GetString("MsgNewObject"));
                       #endif
                   }
            }
           idleTimer.Stop();
      }
     // Call the Activate method if possible.
    if (obj is IObjectControl)
   {
         ((IObjectControl)obj).Activate();
   }
   return obj;
}
throw new TimeoutException(
ResourceHelper.GetString("ExObjectCreationTimeout"));
}

カスタム ReleaseInstance 実装は、解放されたインスタンスをプールに戻し、ActiveObjectsCount 値をデクリメントします。 EndpointDispatcher はこれらのメソッドをさまざまなスレッドから呼び出すので、ObjectPoolInstanceProvider クラスのクラス レベル メンバーへの同期アクセスが必要となります。

public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
    lock (poolLock)
    {
        // Check whether the object can be pooled.
        // Call the Deactivate method if possible.
        if (instance is IObjectControl)
        {
            IObjectControl objectControl = (IObjectControl)instance;
            objectControl.Deactivate();

            if (objectControl.CanBePooled)
            {
                pool.Push(instance);

                #if(DEBUG)
                WritePoolMessage(
                    ResourceHelper.GetString("MsgObjectPooled"));
                #endif
            }
            else
            {
                #if(DEBUG)
                WritePoolMessage(
                    ResourceHelper.GetString("MsgObjectWasNotPooled"));
                #endif
            }
        }
        else
        {
            pool.Push(instance);

            #if(DEBUG)
            WritePoolMessage(
                ResourceHelper.GetString("MsgObjectPooled"));
            #endif
        }

        activeObjectsCount--;

        if (activeObjectsCount == 0)
        {
            idleTimer.Start();
        }
    }

    availableCount.Release(1);
}

ReleaseInstance メソッドには、"初期化のクリーンアップ" 機能があります。 通常は、プールにはその有効期間中に最小限の数のオブジェクトが保持されます。 ただし、使用率が非常に高くなり、プールでオブジェクトを追加作成する必要が生じて、その数が構成に指定されている上限に達する可能性があります。 プールが最終的にアクティブでなくなると、そうした過剰なオブジェクトは余分なオーバーヘッドになります。 したがって、activeObjectsCount がゼロに達すると、アイドル タイマが起動し、クリーンアップ サイクルがトリガされて実行されます。

if (activeObjectsCount == 0)
{
    idleTimer.Start();
}

ServiceModel のレイヤ拡張は、次の動作を使用してフックされます。

  • サービスの動作 : サービス ランタイム全体のカスタマイズを実現します。

  • エンドポイントの動作 : EndpointDispatcher を含む、特定のサービス エンドポイントのカスタマイズを実現します。

  • コントラクトの動作 : クライアント上またはサービス上で、それぞれ ClientRuntime クラスまたは DispatchRuntime クラスのカスタマイズを実現します。

  • 操作の動作 : クライアント上またはサービス上のいずれかで、それぞれ ClientOperation クラスまたは DispatchOperation クラスのカスタマイズを実現します。

オブジェクト プール拡張を行うには、エンドポイントの動作またはサービスの動作のどちらかを作成します。 この例ではサービスの動作を使用します。この動作では、オブジェクト プール機能がサービスの各エンドポイントに適用されます。 サービス動作を作成するには、IServiceBehavior インターフェイスを実装します。 ServiceModel にカスタム動作を認識させるには、次のようにいくつかの方法があります。

  • カスタム属性を使用する。

  • カスタム動作をサービス説明の動作コレクションに強制的に追加する。

  • 構成ファイルを拡張する。

このサンプルではカスタム属性を使用します。 ServiceHost が構築されると、サービスの種類の定義で使用されている属性が調べられ、使用可能な動作がサービス説明の動作コレクションに追加されます。

IServiceBehavior インターフェイスには、Validate,AddBindingParameters,ApplyDispatchBehavior の 3 つのメソッドがあります。 これらのメソッドは、ServiceHost の初期化中に WCF によって呼び出されます。 最初に、IServiceBehavior.Validate が呼び出されます。このメソッドによってサービスの不整合性を検査できます。 次に、IServiceBehavior.AddBindingParameters が呼び出されます。このメソッドは、非常に高度なシナリオでのみ必要です。 最後に、IServiceBehavior.ApplyDispatchBehavior が呼び出されます。このメソッドはランタイムを構成します。 次のパラメータは、IServiceBehavior.ApplyDispatchBehavior に渡されます。

  • Description : このパラメータは、サービス全体のサービスの説明を提供します。 これを使用すると、サービスのエンドポイント、コントラクト、バインディング、およびサービスに関連するその他のデータに関する説明データを検査できます。

  • ServiceHostBase : このパラメータは、現在初期化中の ServiceHostBase を提供します。

カスタム IServiceBehavior 実装では、ObjectPoolInstanceProvider の新しいインスタンスがインスタンス化され、InstanceProvider に関連付けられた各 EndpointDispatcher 内の ServiceHostBase プロパティに割り当てられます。

public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
    if (enabled)
    {
        // Create an instance of the ObjectPoolInstanceProvider.
        instanceProvider = new ObjectPoolInstanceProvider(description.ServiceType,
        maxPoolSize, minPoolSize, creationTimeout);

        // Assign our instance provider to Dispatch behavior in each
        // endpoint.
        foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
        {
             ChannelDispatcher cd = cdb as ChannelDispatcher;
             if (cd != null)
             {
                 foreach (EndpointDispatcher ed in cd.Endpoints)
                 {
                        ed.DispatchRuntime.InstanceProvider = instanceProvider;
                 }
             }
         }
     }
}

IServiceBehavior 実装のほかにも、ObjectPoolingAttribute クラスには属性引数を使用してオブジェクト プールをカスタマイズするいくつかのメンバがあります。 こうしたメンバには MaxSizeMinSizeEnabledCreationTimeout などがあり、.NET Enterprise Services で提供されるオブジェクト プール機能のセットに一致します。

オブジェクト プールの動作は、新しく作成されたカスタムの ObjectPooling 属性を使用してサービス実装に注釈を付けることで、WCF サービスに追加できるようになりました。

[ObjectPooling(MaxSize=1024, MinSize=10, CreationTimeout=30000]
public class PoolService : IPoolService
{
  // …
}

アクティブ化と非アクティブ化のフック

オブジェクト プールの主な目的は、比較的負荷のかかる作成および初期化を伴う有効期間の短いオブジェクトを最適化することです。 そのため、オブジェクト プールが適切に使用された場合は、アプリケーションのパフォーマンスを大幅に向上させることができます。 オブジェクトはプールから返されるので、コンストラクタが呼び出されるのは 1 回だけです。 ただし一部のアプリケーションでは、単一のコンテキスト内で使用されるリソースを初期化してクリーンアップできるようにするために、ある一定のレベルの制御が必要になります。 たとえば、一連の計算に使用されているオブジェクトは、次の計算を実行する前に、そのオブジェクトのプライベート フィールドをリセットできます。 Enterprise Services では、オブジェクト開発者が Activate ベース クラスの Deactivate メソッドおよび ServicedComponent メソッドをオーバーライドすることにより、コンテキスト固有のこの種の初期化が実現されます。

オブジェクト プールは、オブジェクトがプールから返される直前に Activate メソッドを呼び出します。 オブジェクトがプールに返される際には Deactivate メソッドが呼び出されます。 ServicedComponent ベース クラスには、boolean という CanBePooled プロパティもあります。このプロパティを使用すると、オブジェクトをさらにプールできるかどうかをプールに通知できます。

このサンプルではこの機能に似た動作が行われるように、前述のメンバを持つパブリック インターフェイス (IObjectControl) を宣言しています。 このインターフェイスは、コンテキスト固有の初期化を提供するためのサービス クラスによって実装されます。 IInstanceProvider の実装は、これらの要件を満たすように変更する必要があります。 ここで、GetInstance メソッドを呼び出してオブジェクトを取得するたびに、オブジェクトが IObjectControl. を実装しているかどうかをチェックする必要があります。実装されている場合は、Activate メソッドを正しく呼び出す必要があります。

if (obj is IObjectControl)
{
    ((IObjectControl)obj).Activate();
}

オブジェクトをプールに返すときには、そのオブジェクトをプールに追加し直す前に、CanBePooled プロパティのチェックが必要です。

if (instance is IObjectControl)
{
    IObjectControl objectControl = (IObjectControl)instance;
    objectControl.Deactivate();
    if (objectControl.CanBePooled)
    {
       pool.Push(instance);
    }
}

オブジェクトをプールできるかどうかはサービス開発者が決定できるので、指定時間におけるプール内のオブジェクト数は、最低サイズ未満になる場合があります。 したがって、オブジェクト数が最低レベル未満になっているかどうかをチェックし、クリーンアップ手順において必要な初期化を実行する必要があります。

// Remove the surplus objects.
if (pool.Count > minPoolSize)
{
  // Clean the surplus objects.
}
else if (pool.Count < minPoolSize)
{
  // Reinitialize the missing objects.
  while(pool.Count != minPoolSize)
  {
    pool.Push(CreateNewPoolObject());
  }
}

このサンプルを実行すると、操作要求と応答がサービスとクライアントの両方のコンソール ウィンドウに表示されます。 どちらかのコンソールで Enter キーを押すと、サービスとクライアントがどちらもシャットダウンされます。

サンプルをセットアップ、ビルド、および実行するには

  1. Windows Communication Foundation サンプルの 1 回限りのセットアップの手順を実行したことを確認します。

  2. ソリューションをビルドするには、「Windows Communication Foundation サンプルのビルド」の手順に従います。

  3. 単一または複数コンピューター構成でサンプルを実行するには、「Windows Communication Foundation サンプルの実行」の手順に従います。