Sessions, Instancing and Concurrency in WCF Services

Robert Green

MCW Technologies

Download

Articles in this series

Introduction

Before you can build a WCF service, you need to ask some questions. The first question, of course, is what does the service do? The next question is how will the service do what it does? You can then design the service and create service and data contracts.

Before you write the code to implement the service’s methods and do the work, you should ask some additional questions relating to how clients will call the service. For example:

  • When clients call the service, will they call one method or more than one method?
  • If they call more than one method, how much time elapses between method calls?
  • If they call more than one method, does the service need to maintain state between calls?
  • Will clients call the service frequently or infrequently?
  • How many clients at a time will call the service?

The answers to these questions determine your use of sessions, instancing and concurrency in the service. A session groups messages into a conversation. If you need to maintain state between method calls, you will need to create a session.

Do you need sessions? Suppose you create a service the returns stock quotes. In the client application, a user enters a stock symbol and clicks the Get Quote button. The client calls the GetQuote method of the service, which returns the stock quote. The user can enter another stock symbol and get another quote. Unless there is a need to keep track of what quotes the user previously retrieved, there is no need to keep state and therefore no need for a session.

Now suppose you create a service to enable online shopping. In the client application, a user selects a product from a list and clicks the Add To Cart button. The user can add multiple items to the shopping cart and eventually check out and make a purchase. Here, you absolutely need to maintain state and will typically use sessions, although you can maintain state manually and not use sessions.

Instancing refers to the lifetime of a service instance. When a client calls a service, the WCF runtime creates a service object to handle requests. You can control how long this object remains active. In the case of the stock quote service, you could specify that the runtime should create a new service object for each call. This would reduce overall memory consumption. In the case of the online shopping service, you could specify that the runtime should maintain the service object for as long as it takes the user to complete the purchase, in other words, for the duration of the session.

Concurrency refers to the number of threads executing at the same time in a service instance. By default, one thread executes, but if clients call multiple methods and each takes more than a short amount of time, you might want to use multiple threads.

In this tutorial, you will see how to manage sessions, instancing and concurrency in WCF services. You will see the default settings for each of these and see how to change these settings.

Review the First Sample Application

This tutorial includes two sample applications. The first demonstrates sessions and instancing. In Visual Studio 2008, select File | Open | Project/Solution to display the Open Project dialog box. Navigate to the folder where you downloaded this tutorial’s sample project. Select SessionsDemo.sln and click OK to open the project. The sample application includes three projects. The ContractorServiceLibrary project represents a WCF service that clients can call to request the services of a general contractor for various home improvement projects. The ConsoleHost project uses a console application to host the service. The WindowsClient project contains the user interface.

Press F5 to run the application. When the console window appears, confirm that the host application is listening for client requests using the NetTcpBinding, WSHttpBinding and BasicHttpBinding bindings. In the Project Requests form, click Get projects. The form calls the GetProjects method of the WCF service to retrieve a list of projects the contractor offers. The form displays the projects, along with minimum project costs (see Figure 1).

Figure 1. You called the WCF service to retrieve the projects the contractor offers.

Select a project and click Add request. Dismiss the message confirming the request. Repeat this for two additional projects. Each time you click the button the form calls the RequestProject method of the WCF service. The service maintains a list of projects you requested. Click Show requests. The form calls the GetRequestedProjects method of the service and retrieves the list of projects you requested. The form displays the projects (see Figure 2).

Figure 2. You requested these three projects.

By default, the client and service communicate over TCP. You will now communicate over HTTP. Select WsHttpBinding in the Select a binding group and click Start over. As you did before, select a number of projects and then click Show requests. The form should display your selections.

Now select BasicHttpBinding in the Select a binding group and click Start over. Select a project and click Add request. This causes an exception and Visual Studio displays the Exception Assistant (see Figure 3). Select Debug | Stop Debugging to stop the application.

Figure 3. Calling the RequestProject method causes this exception.

When you clicked the Get projects button in the form, you called the GetProjects method of the service. This initialized the projects and requestedProjects fields, both of which are generic lists of instances of the Project class.

// C#

private Project project = null;

private List<Project> projects = null;

private List<Project> requestedProjects = null;

public List<Project> GetProjects()

{

  projects = new List<Project>();

  requestedProjects = new List<Project>();

' Visual Basic

Private _project As Project = Nothing

Private projects As List(Of Project) = Nothing

Private requestedProjects As List(Of Project) = Nothing

Public Function GetProjects() As _

  System.Collections.Generic.List(Of Project) _

  Implements IContractorService.GetProjects

  projects = New List(Of Project)

  requestedProjects = New List(Of Project)

When you clicked the Add request button, you called the RequestProject method of the service and passed to it an instance of the Project class. As shown in Figure 4, this instance contains the selected project name and cost. The service code then attempted to add this to requestedProjects. This caused the exception because the requestedProjects field is null (C#) or Nothing (Visual Basic).

Figure 4. The requestedProjects field is null and that caused the exception to occur.

When you communicated using the NetTcpBinding and WSHttpBinding bindings, the application ran without error. When you use those bindings, the WCF runtime enables sessions. The first time a client calls the service, the WCF runtime creates a service object to handle requests. The service keeps the object active for subsequent calls by the client. The service maintains state and keeps requestedProjects in memory.

The BasicHttpBinding binding does not support sessions. Each time a client calls the service, the WCF runtime creates a new service object. Therefore, the service does not keep any variables in memory. That caused the exception you saw. Other bindings that do not support sessions are NetMsmqBinding, NetPeerTcpBinding and MsmqIntegrationBinding.

Require Sessions

The App.config file in the ConsoleHost project defines three endpoints for the WCF service, one each for the NetTcpBinding, WSHttpBinding and BasicHttpBinding bindings. A primary benefit of defining endpoints in configuration files is that you do not have to rebuild the application to change endpoints. A downside is that you lose some control over what endpoints the service uses. The code in the service assumes that the service will maintain state.

Ideally, you would prevent anyone from hosting or calling the service using a binding that does not support sessions. In the Solution Explorer, double-click IContractorService. To require sessions, add the following code in bold:

// C#

[ServiceContract(SessionMode=SessionMode.Required)]

public interface IContractorService

' Visual Basic

<ServiceContract(SessionMode:=SessionMode.Required)> _

Public Interface IContractorService

Press F5 to run the application. This causes an exception and Visual Studio displays the Exception Assistant (see Figure 5). Select Debug | Stop Debugging to stop the application.

Figure 5. The service requires bindings that support sessions.

At runtime, the ServiceHost object checks for a configuration file. If there is one and it contains endpoint definitions, the ServiceHost object uses those. When you call the Open method of the ServiceHost class, the host starts listening for client calls. At that point, an exception occurs because the configuration file defines an endpoint that does not support sessions and the service requires sessions.

In the Solution Explorer, double-click the App.config file in the ConsoleHost project. Comment out or delete the following XML that defines the endpoint based on the BasicHttpBinding binding:

<endpoint

  address="https://localhost:8080/IContractorService"

  binding="basicHttpBinding"

  bindingConfiguration=""

  name="ContractorService_BasicHttp"

  contract="ContractorServiceLibrary.IContractorService" />

Save your changes. Press F5 to run the application. When the console window appears, confirm that the host application is listening for client requests using the NetTcpBinding and WSHttpBinding bindings. Close the form and press any key in the console window to stop the service.

The next time you update the service reference in the WindowsClient project, Visual Studio will remove from the App.config file the endpoint based on the BasicHttpBinding binding. You could then remove the BasicHttpBinding option button from the form and only communicate using the NetTcpBinding and WSHttpBinding bindings.

Control Session Timeout Durations

By default, sessions time out after ten minutes. You can increase or decrease this amount by changing the value of the ReceiveTimeout property of each binding. Return to the App.config file in the ConsoleHost project. Add the following code in bold to specify a ten second timeout for the NetTcpBinding and WSHttpBinding bindings:

    </serviceBehaviors>

  </behaviors>

  <bindings>

    <netTcpBinding>

      <binding name="tcpBinding"

               receiveTimeout="00:00:10" />

    </netTcpBinding>

    <wsHttpBinding>

      <binding name="wsHttpBinding"

               receiveTimeout="00:00:10" />

    </wsHttpBinding>

  </bindings>

</system.serviceModel>

Add the following code in bold to apply this setting to the bindings specified in the endpoints:

<endpoint

  address="net.tcp://localhost:9000/ContractorService"

  binding="netTcpBinding"

  bindingConfiguration="tcpBinding"

  name="ContractorService_Tcp"

  contract="ContractorServiceLibrary.IContractorService" />

<endpoint

  address="https://localhost:8090/ContractorService"

  binding="wsHttpBinding"

  bindingConfiguration="wsHttpBinding"

  name="ContractorService_WsHttp"

  contract="ContractorServiceLibrary.IContractorService" />

Save your changes. Press CTRL+F5 to run the application. When the console window appears, confirm that the host application is listening for client requests. In the Project Requests form, click Get projects. Select a project and click Add request. Dismiss the message confirming the request.

Wait more than ten seconds and then select a project and click Add request. You should see the error message shown in Figure 6. Click OK to dismiss the message.

Figure 6. The session has timed out so calling the service causes an exception.

Click Add request a second time. You should see the error message shown in Figure 7. Click OK to dismiss the message. When the session timed out, the connection to the service closed. Therefore, the communication channel is in a faulted state and the client can’t communicate with the service.

Figure 7. The communication channel is in a faulted state so you cannot communicate with the service.

Click Start over. This recreates the proxy and establishes a new communication channel to the service. It also starts a new session. Select a project and click Add request. Dismiss the message confirming the request. Within ten seconds, select another project and click Add request. Click Show requests. The form should display the two projects you requested. Close the form. In the console window, press any key to stop the service.

Check for a Faulted Communcation Channel

In this application, you should check if the communication channel is in a faulted state before calling the service. To do that, you can read the State property of the proxy. In the Solution Explorer, right-click Form1 and select View Code. Add the following code to the beginning of the addRequestButton_Click and showRequestButton_Click methods:

// C#

if (proxy.State == CommunicationState.Faulted)

{

  MessageBox.Show(

    "You have lost your connection to the service. " +

    "Please make your request(s) again.");

  CreateProxy();

  proxy.StartOver();

  return;

}

' Visual Basic

If proxy.State = CommunicationState.Faulted Then

  MessageBox.Show( _

    "You have lost your connection to the service. " & _

    "Please make your request(s) again.")

  CreateProxy()

  proxy.StartOver()

  Exit Sub

End If

Save your changes. Press CTRL+F5 to run the application. When the console window appears, confirm that the host application is listening for client requests. In the Project Requests form, click Get projects. Select a project and click Add request. Dismiss the message confirming the request.

Wait more than ten seconds and then select a project and click Add request. You should see the error message shown in Figure 6. As before, the session has expired and the the communication channel is in a faulted state. This time however, the application displays a more user friendly message. Click OK to dismiss the message.

Click Add request a second time. You should see the error message shown in Figure 8Figure 7. Click OK to dismiss the message. Select a project and click Add request. Dismiss the message confirming the request. Within ten seconds, select another project and click Add request. Click Show requests. The form should display the two projects you requested. Close the form. In the console window, press any key to stop the service.

Figure 8. You can check if the communication channel is in a faulted state.

Control When Sessions Start and Stop

When you use sessions, the WCF runtime creates a service object to handle requests the first time a client calls the service. In this application, that occurs when the user clicks the Get projects button on the form. The service keeps the object active for subsequent calls by the client.

There is no need to have the session start when the user retrieves the list of projects. You do not need to maintain state at that point. You don’t need to maintain state until the user begins making project requests.

You can use the IsInitiating property of the OperationContract attribute to control whether a method can start a session. The default value of this property is true, so the first call to any method starts the session. If you set this property to false for a method, then clients cannot call that method until after they call a method that starts a session.

You can use the IsTerminating property of the OperationContract attribute to control whether a method can end a session. The default value of this property is false. The session only ends when you release the proxy class instance or the session times out. If you set this property to true for a method, then clients can end the session by calling that method.

Return to the IContractorService file and add the following code in bold to control when sessions start and stop:

// C#

[OperationContract(IsInitiating=true, IsTerminating=true)]

List<Project> GetProjects();

[OperationContract(IsInitiating=false, IsTerminating=false)]

void RequestProject(Project project);

[OperationContract(IsInitiating=false, IsTerminating=true)]

List<Project> GetRequestedProjects();

[OperationContract(IsInitiating=true, IsTerminating=false)]

void StartOver();

' Visual Basic

<OperationContract(IsInitiating:=True, IsTerminating:=True)> _

Function GetProjects() As List(Of Project)

<OperationContract(IsInitiating:=False, IsTerminating:=False)> _

Sub RequestProject(ByVal _project As Project)

<OperationContract(IsInitiating:=False, IsTerminating:=True)> _

Function GetRequestedProjects() As List(Of Project)

<OperationContract(IsInitiating:=True, IsTerminating:=False)> _

Sub StartOver()

The client calls the GetProjects method to get a list of projects. This does not require state so both the IsInitiating and IsTerminating properties are set to true.

The StartOver, RequestProject and GetRequestedProjects methods require state and therefore should be part of a session. When the client application calls StartOver, the WCF runtime creates a service object and starts a session. The session remains open for each call to the RequestProject method. The session ends when the client calls the GetRequestedProjects method.

Save your changes. Press CTRL+F5 to run the application. When the console window appears, confirm that the host application is listening for client requests. In the Project Requests form, click Get projects. Select a project and click Add request. You should see the error message shown in Figure 9Figure 7. Click OK to dismiss the message. Close the form. In the console window, press any key to stop the service.

Figure 9. The communication channel has terminated.

When you clicked the Get projects button, the WCF runtime created a service object, started a session, ran the method, stopped the session and then released the service object. You then clicked the Add request button, which used the same instance of the proxy class to communicate with the service. However, because the service instance no longer exists, the communication channel used by the proxy no longer exists either. After you call a method that terminates a session, you must recreate the proxy.

Return to the Form1 code file. Add the following declaration within the Form1 class:

// C#

private bool newRequests = true;

' Visual Basic

Private newRequests As Boolean = True

Add the following code to the top of the addRequestButton_Click method to recreate the proxy the first time the user clicks the Add request button:

// C#

if (newRequests == true)

{

  CreateProxy();

  proxy.StartOver();

  newRequests = false;

}

' Visual Basic

If newRequests = True Then

  CreateProxy()

  proxy.StartOver()

  newRequests = False

End If

Add the following code to the end of the Try block in the getProjectsButton_Click method and at the end of the startOverButton_Click method:

// C#

newRequests = true;

' Visual Basic

newRequests = True

Save your changes. Press CTRL+F5 to run the application. When the console window appears, confirm that the host application is listening for client requests. In the Project Requests form, click Get projects. Select a project and click Add request. Dismiss the message confirming the request. Within ten seconds, select another project and click Add request. Click Show requests. The form should display the two projects you requested. Close the form. In the console window, press any key to stop the service.

When you clicked the Add request button, you created a new proxy and started a session. That session ended when you clicked the Show requests button.

Configure Instancing

As mentioned earlier, instancing refers to the lifetime of a service instance. You can control instancing by setting the InstanceContextMode property of the ServiceBehavior attribute. There are three possible values for this property:

  • PerSession. The WCF runtime creates a new service object the first time a client calls the service. It keeps the object active for subsequent calls by the client. The runtime releases the object when the session ends. This is the default value for this property.
  • PerCall. The WCF runtime creates a new service object each time a client calls the service. It releases the object after the call.
  • Single. The WCF runtime creates a new service object the first time a client calls the service. It keeps the object active for subsequent calls by any client.

To change the instancing of the sample WCF service, you could make the following change in bold in the ContractorService file:

// C#

[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]

  public class ContractorService : IContractorService

' Visual Basic

<ServiceBehavior( _

  InstanceContextMode:=InstanceContextMode.PerCall)> _

Public Class ContractorService

  Implements IContractorService

The benefit of using PerCall instancing is that you gain higher throughput and lower resource usage. If you wanted these benefits and also needed to maintain state in your service, you would need to maintain state manually. You could do this by storing information in a database or XML files.

Managing Concurrency

Concurrency refers to the number of threads executing at the same time in a service instance. You can control concurrency by setting the ConcurrencyMode property of the ServiceBehavior attribute. There are three possible values for this property:

  • Single. One thread at a time can access the service object. This is the default value for this property.
  • Reentrant. One thread at a time can access the service object but the thread can exit and reenter.
  • Multiple. More than one thread at a time can access the service object.

By default, only one thread at a time can access the service object. The first call to the service locks the service object. The service object queues additional calls in the order it receives them. When the first call finishes, the next call in the queue locks the service. The benefit to this approach is that there is no risk of two calls reading the same memory and changing the same data.

If the service makes callbacks to the client, you can set the ConcurrencyMode property to Reentrant. When the client calls the service, it locks the service object. The service object releases the lock when the call finishes. When the service calls back to the client, it locks the service object and uses the same thread. The upcoming Messaging Patterns tutorial in this series covers callbacks.

If you set the ConcurrencyMode property to Multiple, more than one thread at a time can access the service object. The WCF runtime assigns a thread to each client call and they execute simultaneously. The benefit to this approach is increased throughput, particularly in situations where methods take an appreciable amount of time to run.

To explore concurrency, select File | Open | Project/Solution to display the Open Project dialog box. Navigate to the folder where you downloaded this tutorial’s sample project. Select ConcurrencyDemo.sln and click OK to open the project. The sample application includes three projects. The ConcurrencyDemo ServiceLibrary project represents a WCF service that clients can call to buy and sell stocks.

The BuyStock and SellStock methods of the service take as a parameter a stock symbol and a quantity. The IsOneWay property of the OperationContract attribute is set to true for both of these methods. The client can call each method and not wait for it to complete before making the next call to the service. The upcoming Messaging Patterns tutorial in this series covers one-way operations in more detail.

// C#

[OperationContract(IsOneWay = true)]

void BuyStock(string stock, int quantity);

[OperationContract(IsOneWay = true)]

void SellStock(string stock, int quantity);

' Visual Basic

<OperationContract(IsOneWay:=True)> _

Sub BuyStock(ByVal stock As String, ByVal quantity As Integer)

<OperationContract(IsOneWay:=True)> _

Sub SellStock(ByVal stock As String, ByVal quantity As Integer)

For the purposes of this demo, it takes five seconds to complete the purchase or sale of a stock.

// C#

public void BuyStock(string stock, int quantity)

{

  Console.WriteLine("Time {0:T}", DateTime.Now);

  System.Threading.Thread.Sleep(new TimeSpan(0, 0, 5));

  Console.WriteLine(String.Format(

    "{0} shares of {1} were bought", quantity, stock));

  Console.WriteLine("Time {0:T}\n", DateTime.Now);

}

public void SellStock(string stock, int quantity)

{

  Console.WriteLine("Time {0:T}", DateTime.Now);

  System.Threading.Thread.Sleep(new TimeSpan(0, 0, 5));

  Console.WriteLine(String.Format(

    "{0} shares of {1} were sold", quantity, stock));

  Console.WriteLine("Time {0:T}\n", DateTime.Now);

}

' Visual Basic

Public Sub BuyStock(ByVal stock As String, _

  ByVal quantity As Integer) _

  Implements IConcurrencyDemoService.BuyStock

  Console.WriteLine("Time {0:T}", DateTime.Now)

  System.Threading.Thread.Sleep(New TimeSpan(0, 0, 5))

  Console.WriteLine(String.Format( _

    "{0} shares of {1} were bought", quantity, stock))

  Console.WriteLine("Time {0:T}" & vbCrLf, DateTime.Now)

End Sub

Public Sub SellStock(ByVal stock As String, _

  ByVal quantity As Integer) Implements _

  IConcurrencyDemoService.SellStock

  Console.WriteLine("Time {0:T}", DateTime.Now)

  System.Threading.Thread.Sleep(New TimeSpan(0, 0, 5))

  Console.WriteLine(String.Format( _

    "{0} shares of {1} were sold", quantity, stock))

  Console.WriteLine("Time {0:T}" & vbCrLf, DateTime.Now)

End Sub

Press F5 to run the application. When the console window appears, confirm that the host application is listening for client requests. In the form, click Buy and sell stock. The form calls the BuyStock method three times to buy three different stocks. It then calls the SellStock method three times to sell three different stocks. The console window displays the following:

Time 11:23:41 AM

100 shares of MSFT were bought

Time 11:23:46 AM

Time 11:23:46 AM

50 shares of GOOG were bought

Time 11:23:51 AM

Time 11:23:51 AM

500 shares of F were bought

Time 11:23:56 AM

Time 11:23:56 AM

100 shares of AAPL were sold

Time 11:24:01 AM

Time 11:24:01 AM

300 shares of ORCL were sold

Time 11:24:06 AM

Time 11:24:06 AM

75 shares of GM were sold

Time 11:24:11 AM

The form makes three calls to the BuyStock method and then three calls to the SellStock method. Each method call takes five seconds to execute and they execute one after the other, so the total elapsed time is thirty seconds.

To achieve better throughput, you can configure the service to enable multiple threads. To change the concurrency of the sample WCF service, make the following change in bold in the ConcurrencyDemoService file:

// C#

[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Multiple)]

public class ConcurrencyDemoService : IConcurrencyDemoService

' Visual Basic

<ServiceBehavior( _

  ConcurrencyMode:=ConcurrencyMode.Multiple)> _

Public Class ConcurrencyDemoService

  Implements IConcurrencyDemoService

Save your changes and build the solution. Press F5 to run the application. When the console window appears, confirm that the host application is listening for client requests. In the form, click Buy and sell stock. The console window displays the following:

Time 11:33:43 AM

Time 11:33:44 AM

Time 11:33:44 AM

Time 11:33:45 AM

Time 11:33:45 AM

Time 11:33:46 AM

100 shares of MSFT were bought

Time 11:33:48 AM

50 shares of GOOG were bought

Time 11:33:49 AM

500 shares of F were bought

Time 11:33:49 AM

100 shares of AAPL were sold

Time 11:33:50 AM

300 shares of ORCL were sold

Time 11:33:50 AM

75 shares of GM were sold

Time 11:33:51 AM

The form makes three calls to the BuyStock method and then three calls to the SellStock method. Each method call takes five seconds to execute, but they execute at the same time and the elapsed time is only eight seconds.

Conclusion

In this tutorial, you have explored sessions, instancing and concurrency in WCF services. You have seen the default settings for each of these and how to configure services to behave differently.

As you design and build WCF services, spend time up front asking questions about how clients will use the service. Decide whether the default settings are appropriate or if you need to make changes to the sessions, instancing or concurrency settings. These changes are relatively straightforward to make and may gain you significant benefits in your scenarios.

About the Author

Robert Green is a developer, writer, and trainer. He is a senior consultant with MCW Technologies. Robert is a Visual Studio Tools for the Office system MVP and is a co-author of AppDev courseware for Microsoft Visual Basic, Microsoft Visual C#, LINQ and Microsoft Windows Workflow Foundation. Before joining MCW, Robert worked at Microsoft as a Product Manager and also a Program Manager.