カスタム有効期間

このサンプルでは、WCF の共有サービス インスタンスにカスタムの有効期間サービスを提供する Windows Communication Foundation (WCF) 拡張機能の作成方法を示します。

Ee960156.note(ja-jp,VS.100).gif注 :
このサンプルのセットアップ手順とビルド手順については、このトピックの最後を参照してください。

共有インスタンス化

WCF では、サービス インスタンス用にいくつかのインスタンス化モードが用意されています。このトピックで説明する共有インスタンス化モードでは、サービス インスタンスを複数のチャネルで共有できます。クライアントは、インスタンスのエンドポイント アドレスをローカルで解決するか、サービスのファクトリ メソッドと通信して実行中のインスタンスのエンドポイント アドレスを取得することができます。エンドポイント アドレスを取得したら、新しいチャネルを作成して通信を開始することができます。次のコード例は、クライアント アプリケーションが既存のサービス インスタンスへの新しいチャネルを作成する方法を示しています。

// Create the first channel.
IEchoService proxy = channelFactory.CreateChannel();

// Resolve the instance.
EndpointAddress epa = ((IClientChannel)proxy).ResolveInstance();

// Create new channel factory with the endpoint address resolved by 
// previous statement.
ChannelFactory<IEchoService> channelFactory2 =
                new ChannelFactory<IEchoService>("echoservice",
                epa);

// Create the second channel to the same instance.
IEchoService proxy2 = channelFactory2.CreateChannel(); 

他のインスタンス化モードとは異なり、共有インスタンス化モードでは、独特の方法でサービス インスタンスを解放します。インスタンスのすべてのチャネルが閉じると、サービスの WCF ランタイムによってタイマーが開始されます。タイムアウトになるまでに誰も接続しなかった場合、WCF はインスタンスを解放してリソースを要求します。切り離し手順の一環として、WCF は、インスタンスを解放する前にすべての IShareableInstanceContextLifetime 実装の IsIdle メソッドを呼び出します。そのすべてが true を返す場合は、インスタンスが解放されます。それ以外の場合、IShareableInstanceContextLifetime 実装は、コールバック メソッドを使用してアイドル状態であることをDispatcherに通知します。

既定では、InstanceContext のアイドル タイムアウト値は 1 分です。ただし、このサンプルでは、カスタム拡張機能を使用してこれを延長する方法を示します。

InstanceContext の拡張

WCF の InstanceContext は、サービス インスタンスとDispatcherの間のリンクです。WCF では、拡張可能オブジェクト パターンを使用して新しい状態または動作を追加することで、このランタイム コンポーネントを拡張できます。拡張可能オブジェクト パターンは、WCF では、既存のランタイム クラスに新しい機能を付け加えて拡張するため、またはオブジェクトに新しい状態の機能を追加するために使用されます。拡張可能オブジェクト パターンには、IExtensibleObject<T>IExtension<T>、および IExtensionCollection<T> の 3 つのインターフェイスがあります。

IExtensibleObject<T> インターフェイスは、機能をカスタマイズするための拡張が可能なオブジェクトによって実装されます。

IExtension<T> インターフェイスは、T 型のクラスの拡張が可能なオブジェクトによって実装されます。

最後に、IExtensionCollection<T> インターフェイスは IExtensions のコレクションで、型ごとに IExtensions を取得できます。

したがって、InstanceContext を拡張するには、IExtension インターフェイスを実装する必要があります。このサンプル プロジェクトでは、CustomLeaseExtension クラスにこの実装が含まれています。

class CustomLeaseExtension : IExtension<InstanceContext>
{
}

IExtension インターフェイスには、AttachDetach の 2 つのメソッドが含まれています。名前が示すように、これらの 2 つのメソッドは、ランタイムが InstanceContext クラスのインスタンスに拡張機能を関連付けるときと関連付けを解除するときに呼び出されます。このサンプルでは、Attach メソッドを使用して、拡張機能の現在のインスタンスに属する InstanceContext オブジェクトを追跡します。

InstanceContext owner;

public void Attach(InstanceContext owner)
{
  this.owner = owner; 
}

また、有効期間の延長をサポートするには、必要な実装を拡張機能に追加する必要があります。したがって、ICustomLease インターフェイスを目的のメソッドで宣言し、CustomLeaseExtension クラスに実装します。

interface ICustomLease
{
    bool IsIdle { get; }        
    InstanceContextIdleCallback Callback { get; set; }
}

class CustomLeaseExtension : IExtension<InstanceContext>, ICustomLease
{
}

WCF が IShareableInstanceContextLifetime 実装で IsIdle メソッドを呼び出すと、この呼び出しは CustomLeaseExtensionIsIdle メソッドにルーティングされます。次に、CustomLeaseExtension はそのプライベート状態をチェックし、InstanceContext がアイドル状態かどうかを確認します。アイドル状態の場合は、true を返します。それ以外の場合は、延長された指定の有効期間のタイマーが開始されます。

public bool IsIdle
{
  get
  {
    lock (thisLock)
    {
      if (isIdle)
      {
        return true;
      }
      else
      {
        StartTimer();
        return false;
      }
    }
  }
}

タイマーの Elapsed イベントで、別のクリーンアップ サイクルを開始するためにディスパッチャーのコールバック関数が呼び出されます。

void idleTimer_Elapsed(object sender, ElapsedEventArgs args)
{
    idleTimer.Stop();
    isIdle = true;  
    callback(owner);
}

アイドル状態に移行中のインスタンスに新しいメッセージが届いたときに、実行中のタイマーを更新する方法はありません。

このサンプルでは、IsIdle メソッドの呼び出しを受け取って CustomLeaseExtension にルーティングするために IShareableInstanceContextLifetime を実装します。IShareableInstanceContextLifetime 実装は、CustomLifetimeLease クラスに含まれています。IsIdle メソッドは、WCF がサービス インスタンスを解放するときに呼び出されます。ただし、ServiceBehavior の InstanceContextLifetimes コレクションに存在する特定の ISharedSessionInstance 実装のインスタンスは 1 つだけです。つまり、WCF が IsIdle メソッドをチェックする時点で閉じられている InstanceContext を知る方法はありません。したがって、このサンプルでは、スレッド ロックを使用して IsIdle メソッドへの要求をシリアル化します。

Ee960156.Important(ja-jp,VS.100).gif 注 :
シリアル化はアプリケーションのパフォーマンスに大きな影響を及ぼす可能性があるので、スレッド ロックは使用しないことをお勧めします。

CustomLeaseExtension クラスでプライベート メンバー変数を使用して、IsIdle の値を追跡します。IShareableInstanceContextLifetime の値が取得されるたびに、IsIdle プライベート メンバーが返されて false にリセットされます。ディスパッチャーが NotifyIdle メソッドを呼び出すようにするには、この値を false に設定する必要があります。

public bool IsIdle
{
    get 
    {
       lock (thisLock)
       {
           bool idleCopy = isIdle;
           isIdle = false;
           return idleCopy;
       }
    }
}

ISharedSessionLifetime.IsIdle プロパティが false を返す場合、ディスパッチャーは NotifyIdle メソッドを使用してコールバック関数を登録します。このメソッドは、解放される InstanceContext への参照を受け取ります。したがって、このサンプル コードでは、ICustomLease 型拡張に対してクエリを実行し、拡張状態の ICustomLease.IsIdle プロパティをチェックすることができます。

public void NotifyIdle(InstanceContextIdleCallback callback, 
            InstanceContext instanceContext)
{
    lock (thisLock)
    {
       ICustomLease customLease =
           instanceContext.Extensions.Find<ICustomLease>();
       customLease.Callback = callback; 
       isIdle = customLease.IsIdle;
       if (isIdle)
       {
             callback(instanceContext);
       }
    } 
}

ICustomLease.IsIdle プロパティをチェックする前に、Callback プロパティを設定する必要があります。これは、アイドル状態になったときに CustomLeaseExtension がディスパッチャーに通知するために不可欠です。ICustomLease.IsIdletrue を返す場合は、isIdle プライベート メンバーが CustomLifetimeLease で単に true に設定され、コールバック メソッドが呼び出されます。このコードではロックが使用されているので、他のスレッドではこのプライベート メンバーの値を変更できません。次にディスパッチャーが ISharedSessionLifetime.IsIdle をチェックするときに true が返されて、ディスパッチャーがインスタンスを解放できるようになります。

これでカスタム拡張機能の基礎が完成したので、この拡張機能をサービス モデルにフックする必要があります。CustomLeaseExtension 実装を InstanceContext にフックするために、WCF には、InstanceContext のブートストラップを実行する IInstanceContextInitializer インターフェイスが用意されています。このサンプルでは、CustomLeaseInitializer クラスでこのインターフェイスを実装し、CustomLeaseExtension のインスタンスを唯一のメソッドの初期化から得られる Extensions コレクションに追加します。このメソッドは、InstanceContext の初期化中にディスパッチャーによって呼び出されます。

public void Initialize(InstanceContext instanceContext, Message message)
{
  IExtension<InstanceContext> customLeaseExtension =
    new CustomLeaseExtension(timeout);
  instanceContext.Extensions.Add(customLeaseExtension);
}

最後に、IServiceBehavior 実装を使用して、IShareableInstanceContextLifetime 実装と IInstanceContextInitializer 実装がサービス モデルにフックされます。この実装は、CustomLeaseTimeAttribute クラスに配置されています。また、Attribute 基本クラスから派生してこの動作を属性として公開します。IServiceBehavior.ApplyBehavior メソッドで、IInstanceContextInitializer 実装と IShareableInstanceContextLifetime 実装のインスタンスがそれぞれ System.ServiceModel.DispatcherInstanceContextLifetimes コレクションと InstanceContextInitializers コレクションに追加されます。

public void ApplyBehavior(ServiceDescription description, 
           ServiceHostBase serviceHostBase, 
           Collection<DispatchBehavior> behaviors,
           Collection<BindingParameterCollection> parameters)
{
    CustomLifetimeLease customLease = new CustomLifetimeLease();
    CustomLeaseInitializer initializer = 
                new CustomLeaseInitializer(timeout);

    foreach (DispatchBehavior dispatchBehavior in behaviors)
    {
        dispatchBehavior.InstanceContextLifetimes.Add(customLease);
        dispatchBehavior.InstanceContextInitializers.Add(initializer);
    }
}

この動作は、CustomLeaseTime 属性を使用して注釈を付けることにより、サンプル サービス クラスに追加できます。

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Shareable)]
[CustomLeaseTime(Timeout = 20000)]
public class EchoService : IEchoService
{
  //…
}

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

サンプルを設定、ビルド、および実行するには

  1. Windows Communication Foundation サンプルの 1 回限りのセットアップの手順」が実行済みであることを確認します。

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

  3. 単一コンピューター構成か複数コンピューター構成かに応じて、「Running the Windows Communication Foundation Samples」の手順に従います。

Ee960156.Important(ja-jp,VS.100).gif 注 :
サンプルは、既にコンピューターにインストールされている場合があります。続行する前に、次の (既定の) ディレクトリを確認してください。

<InstallDrive>:\WF_WCF_Samples

このディレクトリが存在しない場合は、「.NET Framework 4 向けの Windows Communication Foundation (WCF) および Windows Workflow Foundation (WF) のサンプル」にアクセスして、Windows Communication Foundation (WCF) および WF のサンプルをすべてダウンロードしてください。このサンプルは、次のディレクトリに格納されます。

<InstallDrive>:\WF_WCF_Samples\WCF\Extensibility\Instancing\Lifetime