次の方法で共有


InstanceContextSharing

Download sample

このサンプルでは、IInstanceContextProvider インターフェイスを使用して、複数の呼び出しとクライアント間で InstanceContext オブジェクトを共有する方法を示します。このサンプルでは、クライアント アプリケーションで一意識別子を作成し、それをサービスに送信する方法について説明します。次に、サービスは識別子を特定の InstanceContext オブジェクトに関連付けます。その後、クライアントはこの識別子を別のクライアントに渡すことができます。識別子を渡されたクライアントは、同じサービスに送信されるヘッダー内にコンテキスト識別子を配置できます。サービスはそのコンテキスト識別子を使用して、2 番目の呼び出しを最初のインスタンス コンテキスト オブジェクト (つまりサービス オブジェクト) に関連付けます。

Noteメモ :

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

IInstanceContextProvider インターフェイスを実装して、システムに適切な InstanceContext オブジェクトを提供します。通常、このインターフェイスの実装では、共有セッションのサポート、サービス インスタンス プールの有効化、サービス インスタンスの有効期間の制御、またはクライアント間でのコンテキストのグループ化を実現します。

カスタム IInstanceContextProvider を挿入するには、動作 (IEndpointBehavior または IServiceBehavior など) を作成し、その動作を使用して IInstanceContextProvider オブジェクトを System.ServiceModel.Dispatcher.DispatchRuntime.InstanceContextProvider プロパティに割り当てます。

このサンプルでは、カスタム ShareableAttribute 属性で IServiceBehavior を使用して、IInstanceContextProvider を挿入します。

ShareableAttributeIInstanceContextProvider を実装している CalculatorExtension オブジェクトをインスタンス化します。次に各 EndpointDispatcher を反復処理し、先ほど作成した CalculatorExtension オブジェクトに各 InstanceContextProvider プロパティを設定します。これを次のサンプル コードに示します。

//Apply the custom IInstanceContextProvider to the EndpointDispatcher.DispatchRuntime
public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
    CalculatorExtension extension = new CalculatorExtension();
    foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers)
    {
        ChannelDispatcher dispatcher = dispatcherBase as ChannelDispatcher;
        foreach (EndpointDispatcher endpointDispatcher in dispatcher.Endpoints)
        {
            endpointDispatcher.DispatchRuntime.InstanceContextProvider = extension;
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(extension);
        }
    }
}

クライアントからの各呼び出しは CalculatorExtension オブジェクトを経由して、特定のメッセージに InstanceContext を使用するかどうかを判断します。

このサンプルでは、Client1Client2 の 2 つのクライアントを使用して共有を示します。これら 2 つのクライアントがやり取りする順序を、次のコードに示します。クライアントとサーバーは、どちらも CustomHeader を使用して InstanceContext の一意識別子を伝達します。さらにどちらもランダムな数字を生成するユーティリティを使用して、32 バイトの一意識別子を生成します。

public static class CustomHeader
{
    public static readonly String HeaderName = "InstanceId";
    public static readonly String HeaderNamespace = "http://Microsoft.ServiceModel.Samples/Sharing";
}

static string NewInstanceId()
{
    byte[] random = new byte[256 / 8];
    randomNumberGenerator.GetBytes(random);
    return Convert.ToBase64String(random);
}
  1. Client1OperationContextScope を作成します。これによりヘッダーを追加できるようになります。次に一意識別子を生成し、この識別子を送信ヘッダーのリストに値として追加します。

    //Create a new 1028 bit strong InstanceContextId that we want the server to associate 
    //the InstanceContext that processes all messages from this client.
    String uniqueId = NewInstanceId();
    
    MessageHeader Client1InstanceContextHeader = MessageHeader.CreateHeader(
        CustomHeader.HeaderName,
        CustomHeader.HeaderNamespace,
        uniqueId);
    
    try
    {
        using (new OperationContextScope(client1.InnerChannel))
        {
            //Add the header as a header to the scope so it gets sent for each message.
            OperationContext.Current.OutgoingMessageHeaders.Add(Client1InstanceContextHeader);
            ...
        }
    }
    

    その後、リモート サーバー上で複数の操作を呼び出す DoCalculations を呼び出します。呼び出された各操作では、カスタム ヘッダーと生成された識別子が送信されます。

  2. Client1 は、このチャネル上の最初の呼び出しである Add 操作を呼び出します。

  3. サーバーはメッセージを受信し、チャネルとメッセージと共に CalculatorExtension.GetExistingInstanceContext を呼び出します。この拡張機能は、チャネルが InstanceContext に割り当てられているかどうかをチェックします。割り当てられていない場合は、カスタム ヘッダーの検索を試行し、この識別子の InstanceContext がキャッシュにあるかどうかをチェックします。この場合、キャッシュは空なので null が返されます。この拡張機能は、実際は AddressableInstanceContextInfo (ソース内に後で定義) を保存して InstanceContext に関するその他の情報を保持し、InstanceContext が作成される前に複数のスレッド間を調整します。

    // If the channel has a session, we bind the session to a particular InstanceContext
    // based on the first message, and then route all subsequent messages on that session to
    // the same InstanceContext.
    bool hasSession = (channel.SessionId != null);
    if (hasSession)
    {
        info = channel.Extensions.Find<AddressableInstanceContextInfo>();
        if (info != null)
        {
            ...
        }
    }
    
    // If this is the first message of a session, or is using a datagram channel, look in
    // the message headers to see if there is a header with an instance id.
    int headerIndex = message.Headers.FindHeader(CustomHeader.HeaderName, CustomHeader.HeaderNamespace);
    
    // If there was a header, extract the instanceId.
    string instanceId = null;
    if (headerIndex != -1)
    {
        instanceId = message.Headers.GetHeader<string>(headerIndex);
    }
    
    ...
    
    // Check our table to see if we recognize the instance id.
    lock (this.ThisLock)
    {
        if ((instanceId == null) || !this.contextMap.TryGetValue(instanceId, out info))
        {
            isNew = true;
            ...
        }
        ...
    }
    ...
    if (isNew)
    {
        // This tells WCF to create a new InstanceContext and call InitializeInstanceContext.
        return null;
    }
    
  4. サーバーは新しい InstanceContext を作成して CalculatorExtension.InitializeInstanceContext を呼び出します。InitializeInstanceContext メソッドは、新しく作成された InstanceContext の拡張を通知します。これにより、(セッション チャネルの) チャネルまたは (データグラム チャネルの) キャッシュから AddressableInstanceContextInfo が取得され、InstanceContext の拡張コレクションに追加されます。これは、サーバーが任意の InstanceContext の識別子をすばやく取得できるようにするために行われます。チャネルにセッションがある場合、この拡張機能では、チャネルが閉じる前に Windows Communication Foundation (WCF) が InstanceContext を閉じないように、チャネルは InstanceContextIncomingChannels コレクションに追加されます。このサンプルでは、InstanceContext が明示的に閉じられた場合はキャッシュから削除できるように、このオブジェクトの Closed イベントをフックします。ここで、この InstanceContext を使用して、メッセージの処理とメッセージへの応答を行います。

    if (hasSession)
    {
        // Since this is a new InstanceContext, we could not add the channel in
        // GetExistingInstanceContext, so add it here.
        instanceContext.IncomingChannels.Add(channel);
    
        // If we have a session, we stored the info in the channel, so just look it up
        // there.
        info = channel.Extensions.Find<AddressableInstanceContextInfo>();
    }
    else
    {
        // Otherwise, if we don't have a session, look the info up again in the table.
        ...
    }
    
    // Now that we have the InstanceContext, we can link it to the
    // AddressableInstanceContextInfo and vice versa.
    if (info != null)
    {
        instanceContext.Extensions.Add(info);
        info.SetInstanceContext(instanceContext);
    }
    
    // When the InstanceContext starts closing, remove it from the table.
    //
    // Generally we will already have the lock because Close will happen inside
    // CallIdleCallback.  However, if someone just closes the InstanceContext explicitly
    // before it goes idle, we will not have the lock.  Since modifying Dictionary is not
    // thread-safe, we lock here.
    instanceContext.Closing += delegate(object sender, EventArgs e)
    {
        lock (this.ThisLock)
        {
            this.contextMap.Remove(info.InstanceId);
        }
    };
    
  5. Client1 は、次に 2 番目の操作である Subtract を呼び出します。CalculatorExtension.GetExistingInstanceContext が、新しいメッセージと共にもう一度呼び出されます。そしてヘッダーが取得され、この場合は正常に参照されます。キャッシュから InstanceContext が返されます。WaitForInstance により、最初の呼び出しによる InitializeInstanceContext 呼び出しが正常に完了します。WCF はこの InstanceContext を使用して、残りのメッセージを処理します。

    if (hasSession)
    {
        info = channel.Extensions.Find<AddressableInstanceContextInfo>();
        if (info != null)
        {
            // We may be processing a second message before the first message has finished
            // initializing the InstanceContext.  Wait here until the first message is
            // done.  If the first message has already finished initializing, this returns
            // immediately.
            info.IncrementBusyCount();
            return info.WaitForInstanceContext();
        }
    } 
    
  6. Client1Subtract 操作と Delete 操作を呼び出し、手順 5. を繰り返すことによって同じ InstanceContext を使用します。

  7. 次に別のチャネル (Client2) を作成し、もう一度 OperationContextScope を作成します。その後、同じ識別子をその OutgoingMessageHeaders コレクションに追加します。したがって、Client1 で使用された同じ識別子が、Client2 で作成されたすべての呼び出しでも共に送信されます。

  8. Client2Add()Subtract()Multiply()、および Divide() 操作を呼び出し、手順 7. と同じロジックを使用して、最初のクライアントで作成された InstanceContext によりこれらのすべての操作にサービスを提供します。サーバーは CalculatorExtension.GetExistingInstanceContext を呼び出します。拡張機能はヘッダーを検索し、これに関連付けられている InstanceContext を検索します。この InstanceContext は、メッセージのディスパッチに使用されます。

    // If this is the first message of a session, or is using a datagram channel, look in
    // the message headers to see if there is a header with an instance id.
    int headerIndex = message.Headers.FindHeader(CustomHeader.HeaderName, CustomHeader.HeaderNamespace);
    
    // If there was a header, extract the instanceId.
    string instanceId = null;
    if (headerIndex != -1)
    {
        instanceId = message.Headers.GetHeader<string>(headerIndex);
    }
    
    // Remember if we created a new AddressableInstanceContextInfo.
    bool isNew = false;
    
    // Check our table to see if we recognize the instance id.
    lock (this.ThisLock)
    {
        if ((instanceId == null) || !this.contextMap.TryGetValue(instanceId, out info))
        {
            ...
        }
        ...
    }
    ...
    if (isNew)
    {
        // ...
    }
    else
    {
        InstanceContext instanceContext = info.WaitForInstanceContext();
        ...
        return instanceContext;
    }
    

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

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

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

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

Footer image

Copyright © 2007 by Microsoft Corporation.All rights reserved.