Developing a real-time data flow and control model with WCF

A Windows Communication Foundation (WCF) service is defined through its operations and data contracts. One of the major benefits of WCF is the ease with which a client can create and use these services through the automatically generated proxy classes. The service side is only half of the communications link though. Discovering the correct WCF configuration options that allow a solution to operate properly was not as easy as I thought it would be.

This post describes a specific WCF-based data control and streaming architecture. The primary goal of this service is to provide a continuous data stream (as buffers of short values) from a real-time data acquisition source. The client would then be able to display the data as it became available or store the data when directed by the user. In addition, the service allows the client to both get status information (Getters) and control certain attributes (Setters) of the underlying data source. This is illustrated here:

Real-time architecture

The DataBufferEvent is defined as a one-way callback and continuously delivers data to the client. The IsOneWay property is valid for any operation that does not have a return value and improves network performance by not requiring a return message. The Getters and Setters [for you Java folks, this has nothing to do with JavaBeans] can be called at any time. Changing a data source attribute with a Setter will probably affect the data stream, but it is the responsibility of the data source to ensure data integrity. The underlying transport binding must support duplex operation (e.g. wsDualHttpBinding or netTcpBinding) in order for this scenario to work.

Here is what an example (a sine wave generator) service interface looks like:

namespace SineWaveWCFLib
{
    [ServiceContract(SessionMode = SessionMode.Required,
        CallbackContract = typeof(ISineWaveWCFLibCallback))]
    public interface ISineWaveWCFLib
    {
        // Setters are one-way operations
        [OperationContract(IsOneWay = true)]
        void SetWaveformFrequency(int wfm, double freq);
        [OperationContract(IsOneWay = true)]
        void SetSamplingPeriod(int sper);
        [OperationContract(IsOneWay = true)]
        void SetBufferSize(int count);
        // Getters are two-way operations (default)
        [OperationContract]
        double GetWaveformFrequency(int wfm);
        [OperationContract]
        int GetWaveformCount();
        [OperationContract]
        int GetSamplingPeriod();
    }
    // Define the callback contract interface
    public interface ISineWaveWCFLibCallback
    {
        [OperationContract(IsOneWay=true)]
        void DataBufferEvent(short [] data);
    }
}

The service class is implemented as follows:

[ServiceBehavior(InstanceContextMode =InstanceContextMode.PerSession,
        ConcurrencyMode=ConcurrencyMode.Multiple)]
public class SineWaveWCFLib : ISineWaveWCFLib
{
    private ISineWaveWCFLibCallback callback = null;
    // Constructor
    SineWaveWCFLib()
    {
        // Initialize COM object and setup serv_DataBufferEvent callback.
        callback = OperationContext.Current.GetCallbackChannel< ISineWaveWCFLibCallback>();
        // Other initialization...
    }
    // When a buffer is ready serv_DataBufferEvent is called by the underlaying COM object
    // that is producing the data. GetDataBuffer turns pOutBuff into a short [] array.
    // None of that code is shown here.
    private void serv_DataBufferEvent(Array pOutBuff)
    {
        callback.DataBufferEvent(GetDataBuffer(pOutBuff));
    }
    // ISineWaveWCFService Members not shown.
}

The InstanceContextMode.PerSession mode is appropriate for this type of interface. Even though there is probably only a single data source, you still want to allow multiple service session instances to provide data simultaneously to different clients. The data source would be responsible for managing the multiple data requesters.

With the service side complete, all the client needs is to do is create the proxy classes (with either Visual Studio or Svcutil), setup the DataBufferEvent callback and call the appropriate control functions. My first client was a Winform application to display the data stream. The problem I ran into is that even though the data callbacks worked properly, I would often see the control functions hang the application when they were invoked.

It took quite a bit of searching around before I found the solution, which is here. You can read the details about the SynchronizationContext issues, but this caused me to spin my wheels for several days. The upside is that in trying to diagnose the problem I learned how to use the Service Trace Viewer Tool (SvcTraceViewer.exe) and the Configuration Editor Tool (SvcConfigEditor.exe, which is in the VS2005 Tools menu).

So after adding the appropriate CallbackBehavior attributes, here are the important parts of the client that allow this WCF model to operate reliably:

public partial class Form1 : Form
{
        private SineWaveWCFLibClient m_client;
        [CallbackBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant,
                           UseSynchronizationContext=false)]
        internal class DataBufferEventHandler : SineWaveWCFLibCallback
        {
            private Form1 m_form;
            public DataBufferEventHandler(Form1 form)
            {
                m_form = form;
            }
            public void DataBufferEvent(BindingList< short> data)
            {
                m_form.UpdateWaveforms(data); // not shown: Uses Invoke to update display
            }
        }
 
        private void Form1_Load(object sender, EventArgs e)
        {
            InstanceContext site = new InstanceContext(new DataBufferEventHandler(this));
            m_client = new SineWaveWCFLibClient(site, "NetTcpBinding_SineWaveWCFLib");
            try
            {
                m_client.SetBufferSize(50);
            }
            catch (EndpointNotFoundException)
            {
                MessageBox.Show("Unable to connect to " + m_client.Endpoint.Address.ToString() +
                                        "!nQuiting application.");
                m_client = null;
                Application.Exit();
            }
        }
}

The first take-away here is that WCF is a complex beast. The second is that even though it’s easy to create proxy classes from a WCF service, you have to understand and take into account both sides of the communications link. It seems so obvious now!

That’s it. This WCF component is just part of a larger project that I’m planning on submitting as an article (with source code) on CodeProject, someday. If you’d like the get a copy of the source before that, just let me know and I’ll send you what I currently have.

Update: Proof of my first take-away: Callbacks, ConcurrencyMode and Windows Clients. Thanks Michele! :-)

9 Responses to “Developing a real-time data flow and control model with WCF”


Comments are currently closed.
Subscribe

Categories

Twitter Updates