有効期間のサンプルでは、共有 WCF サービス インスタンスのカスタム有効期間サービスを提供する Windows Communication Foundation (WCF) 拡張機能を記述する方法を示します。
注
このサンプルのセットアップ手順とビルド手順については、この記事の最後を参照してください。
共有インスタンス化
WCF には、サービス インスタンスに対していくつかのインスタンス化モードが用意されています。 この記事で説明する共有インスタンス モードは、複数のチャネル間でサービス インスタンスを共有する方法を提供します。 クライアントは、サービスのファクトリ メソッドに接続し、新しいチャネルを作成して通信を開始できます。 次のコード スニペットは、クライアント アプリケーションが既存のサービス インスタンスへの新しいチャネルを作成する方法を示しています。
// Create a header for the shared instance id
MessageHeader shareableInstanceContextHeader = MessageHeader.CreateHeader(
CustomHeader.HeaderName,
CustomHeader.HeaderNamespace,
Guid.NewGuid().ToString());
// Create the channel factory
ChannelFactory<IEchoService> channelFactory =
new ChannelFactory<IEchoService>("echoservice");
// Create the first channel
IEchoService proxy = channelFactory.CreateChannel();
// Call an operation to create shared service instance
using (new OperationContextScope((IClientChannel)proxy))
{
OperationContext.Current.OutgoingMessageHeaders.Add(shareableInstanceContextHeader);
Console.WriteLine("Service returned: " + proxy.Echo("Apple"));
}
((IChannel)proxy).Close();
// Create the second channel
IEchoService proxy2 = channelFactory.CreateChannel();
// Call an operation using the same header that will reuse the shared service instance
using (new OperationContextScope((IClientChannel)proxy2))
{
OperationContext.Current.OutgoingMessageHeaders.Add(shareableInstanceContextHeader);
Console.WriteLine("Service returned: " + proxy2.Echo("Apple"));
}
他のインスタンス化モードとは異なり、共有インスタンス化モードには、サービス インスタンスを解放する独自の方法があります。 既定では、すべてのチャネルが InstanceContextに対して閉じられると、WCF サービス ランタイムは、サービス InstanceContextMode が PerCall または PerSessionに構成されているかどうかを確認し、構成されている場合はインスタンスを解放し、リソースを要求します。 カスタム IInstanceContextProvider が使用されている場合、WCF は、インスタンスを解放する前に、プロバイダー実装の IsIdle メソッドを呼び出します。 インスタンスが解放true
IsIdleが返された場合、それ以外の場合、IInstanceContextProvider実装はコールバック メソッドを使用してアイドル状態のDispatcher
に通知します。 これを行うには、プロバイダーの NotifyIdle メソッドを呼び出します。
このサンプルでは、アイドル タイムアウトが 20 秒で InstanceContext のリリースを遅延させる方法を示します。
InstanceContext の拡張
WCF では、 InstanceContext はサービス インスタンスと Dispatcher
の間のリンクです。 WCF では、拡張オブジェクト パターンを使用して新しい状態または動作を追加することで、このランタイム コンポーネントを拡張できます。 拡張可能オブジェクト パターンは、既存のランタイム クラスに新しい機能を付け加えて拡張するため、またはオブジェクトに新しい状態の機能を追加するために WCF で使用されます。 拡張可能なオブジェクト パターンには、 IExtensibleObject<T>、 IExtension<T>、 IExtensionCollection<T>の 3 つのインターフェイスがあります。
IExtensibleObject<T> インターフェイスは、機能をカスタマイズする拡張機能を許可するオブジェクトによって実装されます。
IExtension<T> インターフェイスは、T
型のクラスの拡張である可能性があるオブジェクトによって実装されます。
最後に、IExtensionCollection<T> インターフェイスは、型によってIExtension<T>の実装を取得できるIExtension<T>実装のコレクションです。
そのため、 InstanceContextを拡張するには、 IExtension<T> インターフェイスを実装する必要があります。 このサンプル プロジェクトでは、 CustomLeaseExtension
クラスにこの実装が含まれています。
class CustomLeaseExtension : IExtension<InstanceContext>
{
}
IExtension<T> インターフェイスには、AttachとDetachの 2 つのメソッドがあります。 これらの名前が示すように、ランタイムが拡張機能をアタッチし、 InstanceContext クラスのインスタンスにデタッチすると、これら 2 つのメソッドが呼び出されます。 このサンプルでは、 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 がIInstanceContextProvider実装でIsIdle メソッドを呼び出すと、この呼び出しはCustomLeaseExtension
のIsIdle メソッドにルーティングされます。 次に、 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)
{
lock (thisLock)
{
StopTimer();
isIdle = true;
Utility.WriteMessageToConsole(
ResourceHelper.GetString("MsgLeaseExpired"));
callback(owner);
}
}
アイドル状態に移行されているインスタンスに対して新しいメッセージが到着したときに、実行中のタイマーを更新する方法はありません。
このサンプルでは、IsIdle メソッドの呼び出しをインターセプトし、CustomLeaseExtension
にルーティングするIInstanceContextProviderを実装しています。
IInstanceContextProvider実装は、CustomLifetimeLease
クラスに含まれています。
IsIdle メソッドは、WCF がサービス インスタンスを解放しようとしているときに呼び出されます。 ただし、ServiceBehavior のIInstanceContextProvider コレクションには、特定のISharedSessionInstance
実装のインスタンスが 1 つだけ存在します。 つまり、WCF が IsIdle メソッドをチェックするときにInstanceContextが閉じているかどうかを知る方法はありません。 そのため、このサンプルではスレッド ロックを使用して、 IsIdle メソッドに要求をシリアル化します。
Von Bedeutung
シリアル化はアプリケーションのパフォーマンスに重大な影響を与える可能性があるため、スレッド ロックの使用はお勧めできません。
プライベート メンバー フィールドは、アイドル状態を追跡するために CustomLifetimeLease
クラスで使用され、 IsIdle メソッドによって返されます。
IsIdle メソッドが呼び出されるたびに、isIdle
フィールドが返され、false
にリセットされます。 ディスパッチャーが NotifyIdle メソッドを呼び出すようにするには、この値を false
に設定することが不可欠です。
public bool IsIdle(InstanceContext instanceContext)
{
get
{
lock (thisLock)
{
//...
bool idleCopy = isIdle;
isIdle = false;
return idleCopy;
}
}
}
IInstanceContextProvider.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.IsIdle
がtrue
を返す場合、isIdle
プライベート メンバーは単にCustomLifetimeLease
に設定されてtrue
され、コールバック メソッドが呼び出されます。 コードはロックを保持しているため、他のスレッドはこのプライベート メンバーの値を変更できません。 次に Dispatcher が IInstanceContextProvider.IsIdleを呼び出すと、 true
が返され、Dispatcher がインスタンスを解放できるようになります。
カスタム拡張機能の基礎が完成したので、サービス モデルにフックする必要があります。
CustomLeaseExtension
実装をInstanceContextにフックするために、WCF には、InstanceContextのブートストラップを実行するIInstanceContextInitializer インターフェイスが用意されています。 このサンプルでは、 CustomLeaseInitializer
クラスはこのインターフェイスを実装し、唯一のメソッド初期化から CustomLeaseExtension
のインスタンスを Extensions コレクションに追加します。 このメソッドは、 InstanceContextの初期化中に Dispatcher によって呼び出されます。
public void InitializeInstanceContext(InstanceContext instanceContext,
System.ServiceModel.Channels.Message message, IContextChannel channel)
//...
IExtension<InstanceContext> customLeaseExtension =
new CustomLeaseExtension(timeout, headerId);
instanceContext.Extensions.Add(customLeaseExtension);
}
最後に、 IInstanceContextProvider 実装は、 IServiceBehavior 実装を使用してサービス モデルにフックされます。 この実装は CustomLeaseTimeAttribute
クラスに配置され、この動作を属性として公開するために、 Attribute 基底クラスから派生します。
public void ApplyDispatchBehavior(ServiceDescription description,
ServiceHostBase serviceHostBase)
{
CustomLifetimeLease customLease = new CustomLifetimeLease(timeout);
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
if (cd != null)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
ed.DispatchRuntime.InstanceContextProvider = customLease;
}
}
}
}
この動作は、 CustomLeaseTime
属性を使用して注釈を付けることで、サンプル サービス クラスに追加できます。
[CustomLeaseTime(Timeout = 20000)]
public class EchoService : IEchoService
{
//…
}
サンプルを実行すると、操作の要求と応答がサービスとクライアントの両方のコンソール ウィンドウに表示されます。 各コンソール ウィンドウで Enter キーを押して、サービスとクライアントをシャットダウンします。
サンプルを設定、ビルド、実行するには
Windows Communication Foundation サンプルのOne-Time セットアップ手順を実行していることを確認します。
ソリューションの C# または Visual Basic .NET エディションをビルドするには、「Windows Communication Foundation サンプルのビルド」の手順に従います。
単一または複数のコンピューター間の構成でサンプルを実行するには、「Windows Communication Foundation Samplesの実行」の手順に従います。