Support us .Net Basics C# SQL ASP.NET ADO.NET MVC Slides C# Programs Subscribe Buy DVD

Part 45 - Reentrant concurrency mode in WCF

Suggested Videos
Part 42 - SessionMode Enumeration in WCF
Part 43 - Single concurrency mode in WCF
Part 44 - Multiple concurrency mode in WCF



In this video we will discuss the Reentrant concurrency mode in WCF with an example. 

Reentrant mode allows the WCF service to issue callbacks to the client application. 



Reentrant concurrency mode in WCF

1. The WCF service concurrency mode is set to Single. This means only one thread is allowed to access the service instance.
2. Client makes a request to the WCF Service. The service instance gets locked by the thread that is executing the client request. At this point no other thread can access the service instance, until the current thread has completed and released the lock.
3. While the service instance is processing the client request, the service wants to callback the client. The callback operation is not one way. This means the response for the callback from the client needs to get back to the same service instance, but the service instance is locked and the response from the client cannot reenter and access the service instance. This situation leads to a deadlock.

There are 2 ways to solve this problem.
1. Set the concurrency mode of the WCF service to Reentrant. 

OR

2. Make the callback operation oneway. When the callback operation is made oneway, the service is not expecting a response for the callback from the client, so locking will not be an issue.

Let's understand this with an example
Step 1: Create a class library project with name = Report Service. Delete the autogenerated class1.cs file.

Step 2: Right click on ReportService project in solution explorer and add a WCF service with name = ReportService.

Step 3: Copy and paste the following code in IReportService.cs file
using System.ServiceModel;
namespace ReportService
{
    // Associate callback contract with service contract using CallbackContract attribute
    [ServiceContract(CallbackContract = typeof(IReportServiceCallback))]
    public interface IReportService
    {
        [OperationContract]
        void ProcessReport();
    }

    // This is the callback contract
    public interface IReportServiceCallback
    {
        // Since we have not set IsOnway=true, the operation is Request/Reply operation
        [OperationContract]
        void Progress(int percentageComplete);
    }
}

Step 4: Copy and paste the following code in ReportService.cs file
using System.ServiceModel;
using System.Threading;
namespace ReportService
{
    public class ReportService : IReportService
    {
        public void ProcessReport()
        {
            for (int i = 1; i <= 100; i++)
            {
                // some logic to process the report
                Thread.Sleep(100);
                // Get the callback channel to send messages to the client
                OperationContext.Current.GetCallbackChannel<IReportServiceCallback>().Progress(i);
            }
        }
    }
}

Step 5: Right click on ReportService solution in solution explorer and add a console project with name = Host. We will use this project to host the WCF service.

Step 6: Right click on References folder under Host project and add a reference to ReportService project and System.ServiceModel assembly.

Step 7:  Right click on Host project and add Application Configuration file. This should add App.config file.

Step 8: Copy and paste the following configuration in App.config file
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="mexBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service behaviorConfiguration="mexBehavior"
               name="ReportService.ReportService">
        <endpoint address="ReportService" binding="netTcpBinding"
            contract="ReportService.IReportService" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080" />
            <add baseAddress="net.tcp://localhost:8090" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

Step 9: Copy and paste the following code in Program.cs file in the Host project.
using System.ServiceModel;
using System;
namespace Host
{
    class Program
    {
        public static void Main()
        {
            using (ServiceHost host = new ServiceHost
                (typeof(ReportService.ReportService)))
            {
                host.Open();
                Console.WriteLine("Host started @ " + DateTime.Now.ToString());
                Console.ReadLine();
            }
        }
    }
}

Step 10: Set Host project as the startup project and run the application by pressing CTRL + F5 key. At this point we have the WCF service up and running.

Step 11: Now let's create a client for the WCF service. Create a new windows forms application with name = WindowsClient

Step 12: Add a service reference to ReportService. Right click on References folder and select Add Service Reference. In the Add Service Reference window type Address = http://localhost:8080/ and click GO button. This should bring up the ReportService. In the Namespace textbox type ReportService and click OK.

Step 13: Design Form1 as shown below, with a button and a textbox controls. Set the following properties on button1
Name = btnProcessReport
Text  = Process Report
Reentrant concurrency mode example

Step 14: Double click on the button to generate the "click event handler" method. Copy and paste the following code in Form1.cs file.
using System;
using System.ServiceModel;
using System.Windows.Forms;
namespace WindowsClient
{
    // The Form1 class implements IReportServiceCallback interface
    [CallbackBehavior(UseSynchronizationContext = false)]
    public partial class Form1 : Form, ReportService.IReportServiceCallback
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnProcessReport_Click(object sender, EventArgs e)
        {
            InstanceContext instanceContext = new InstanceContext(this);
            ReportService.ReportServiceClient client = new
                ReportService.ReportServiceClient(instanceContext);

            client.ProcessReport();
        }

        // This method recevies the progress update from the WCF service
        // Report processing percentage is displayed in the textbox control
        public void Progress(int percentageComplete)
        {
            textBox1.Text = percentageComplete.ToString() + " % completed";
        }
    }
}

Step 15: Run client the application by pressing CTRL + F5 and click on "Process Report" button. At this point we will get the following exception.
This operation would deadlock because the reply cannot be received until the current Message completes processing. If you want to allow out-of-order message processing, specify ConcurrencyMode of Reentrant or Multiple on ServiceBehaviorAttribute.

Step 16: To fix the above error,
In ReportService.cs file, set ConcurrencyMode to Reentrant as shown below.
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class ReportService : IReportService

OR

Make callback operation contract OneWay in IReportService.cs file in ReportService project
public interface IReportServiceCallback
{
    [OperationContract(IsOneWay = true)]
    void Progress(int percentageComplete);
}

wcf tutorial

2 comments:

  1. Thank you for great tutorial!
    I have an exception on a client application when it tries to write into the text box:{"Cross-thread operation not valid: Control 'textBox3' accessed from a thread other than the thread it was created on."}. I tried with: [OperationContract(IsOneWay = true)] and and with [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)], but no luck.
    public void ReportProgress(TimeSpan span)
    {
    //message = span.ToString();
    textBox3.Text = "Remaining time is " + span.ToString();
    }
    What am I missing?
    Thank you

    ReplyDelete
  2. Try using this,
    This will help you to atleast remove the issue while training yourself.
    TextBox.CheckForIllegalCrossThreadCalls = false;

    ReplyDelete

If you like this website, please share with your friends on facebook and Google+ and recommend us on google using the g+1 button on the top right hand corner.