Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WCF Service with callbacks coming from background thread?

Here is my situation. I have written a WCF service which calls into one of our vendor's code bases to perform operations, such as Login, Logout, etc. A requirement of this operation is that we have a background thread to receive events as a result of that action. For example, the Login action is sent on the main thread. Then, several events are received back from the vendor service as a result of the login. There can be 1, 2, or several events received. The background thread, which runs on a timer, receives these events and fires an event in the wcf service to notify that a new event has arrived.

I have implemented the WCF service in Duplex mode, and planned to use callbacks to notify the UI that events have arrived. Here is my question: How do I send new events from the background thread to the thread which is executing the service?

Right now, when I call OperationContext.Current.GetCallbackChannel<IMyCallback>(), the OperationContext is null. Is there a standard pattern to get around this?

I am using PerSession as my SessionMode on the ServiceContract.

UPDATE: I thought I'd make my exact scenario clearer by demonstrating how I'm receiving events from the vendor code. My library receives each event, determines what the event is, and fires off an event for that particular occurrence.

I have another project which is a class library specifically for connecting to the vendor service. I'll post the entire implementation of the service to give a clearer picture:

    [ServiceBehavior(
        InstanceContextMode = InstanceContextMode.PerSession
        )]
    public class VendorServer:IVendorServer
    {
private IVendorService _vendorService;  // This is the reference to my class library

        public VendorServer()
        {
_vendorServer = new VendorServer();
_vendorServer.AgentManager.AgentLoggedIn += AgentManager_AgentLoggedIn; // This is the eventhandler for the event which arrives from a background thread

}

        public void Login(string userName, string password, string stationId)
        {
            _vendorService.Login(userName, password, stationId); // This is a direct call from the main thread to the vendor service to log in
        }

    private void AgentManager_AgentLoggedIn(object sender, EventArgs e)
    {

        var agentEvent = new AgentEvent
                             {
                                 AgentEventType = AgentEventType.Login,
                                 EventArgs = e
                             };
    }
}

The AgentEvent object contains the callback as one of its properties, and I was thinking I'd perform the callback like this:

agentEvent.Callback = OperationContext.Current.GetCallbackChannel<ICallback>();

The AgentEvent is an object defined in the service:

[DataContract]
public class AgentEvent
{
    [DataMember]
    public EventArgs EventArgs { get; set; }
    [DataMember]
    public AgentEventType AgentEventType { get; set; }
    [DataMember]
    public DateTime TimeStamp{ get; set; }
    [DataMember]
    public IVendorCallback Callback { get; set; }
}

IVendorCallback looks like this:

    public interface IVendorCallback
    {
        [OperationContract(IsOneWay = true)]
        void SendEvent(AgentEvent agentEvent);
    }

The callback is implemented on the client and uses the EventArgs porperty of the AgentEvent to populate data on the UI. How would I pass the OperationContext.Current instance from the main thread into the background thread?

like image 478
Mark Struzinski Avatar asked Apr 08 '10 20:04

Mark Struzinski


1 Answers

OperationContext.Current is only available on the thread that is actually executing the operation. If you want it to be available to a worker thread, then you need to actually pass a reference to the callback channel to that thread.

So your operation might look something vaguely like:

public class MyService : IMyService
{
    public void Login()
    {
        var callback = 
            OperationContext.Current.GetCallbackChannel<ILoginCallback>();
        ThreadPool.QueueUserWorkItem(s =>
        {
            var status = VendorLibrary.PerformLogin();
            callback.ReportLoginStatus(status);
        });
    }
}

This is a straightforward way of doing it using the ThreadPool and anonymous method variable capturing. If you want to do it with a free-running thread, you'd have to use a ParameterizedThreadStart instead and pass the callback as the parameter.


Update for specific example:

Seems that what's going on here is that the IVendorService uses some event-driven model for callbacks.

Since you're using InstanceContextMode.PerSession, you can actually just store the callback in a private field of the service class itself, then reference that field in your event handler.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class VendorServer : IVendorServer
{
    private IMyCallback callback;
    private IVendorService vendorService;

    public VendorServer()
    {
        callback = OperationContext.Current.GetCallbackChannel<IMyCallback>();
        vendorService = new VendorService();
        vendorService.AgentManager.AgentLoggedIn += AgentManager_AgentLoggedIn;
    }

    public void Login(string userName, string password, string stationId)
    {
        vendorService.Login(userName, password, stationId);
    }

    private void AgentManager_AgentLoggedIn(object sender, EventArgs e)
    {
        callback.ReportLoggedIn(...);
    }
}

If you decide to switch to a different instance mode later on then this won't work, because each client will have a different callback. As long as you keep it in session mode, this should be fine.

like image 182
Aaronaught Avatar answered Sep 21 '22 12:09

Aaronaught