0

I have a publisher / subscriber pattern WCF Duplex ServiceHost that is hosted by a Windows Service. The Windows Service receives events from a separate process. OnEvent I would like to force my WCF Host to publish that data to all subscribed clients. Typically if a Client is calling this is straight forward. But when my Service Host needs to do this - I can't get my head around HOW to do that. I have 2 questions:

1: I do not know how to create a Channel in WCFHost from my Windows Service so that it can use to publish to the Subscribers.

2: I read Creating WCF ChannelFactory so I do know I am creating a DuplexChannelFactory (2 per second ) which might be too much overhead.

Any help examples, hints are greatly appreciated. I am not a WCF expert and currently know more about it than I thought I should have to know in order to use it.

I had read on SO Can I call a Method in a self hosted wcf host locally?

So then I have created a method inside my WCFHost like so:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,
                 AutomaticSessionShutdown = false,
                 IncludeExceptionDetailInFaults = true)]
[CallbackBehavior(UseSynchronizationContext = false, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class ServerHost<TService> : ServiceHost where TService : class
{     
   public T GetDuplexClientChannel<T, Cback>(BindingType bindingType, EndpointAddress endPointAddress)  where T : class 
    {
        ServiceEndpoint sep = GetContractServiceEndPoint<T>(bindingType, endPointAddress);
        lock (_syncRoot)
        {
            DuplexChannelFactory<T> factory = new DuplexChannelFactory<T>(typeof(Cback), sep);
            return factory.CreateChannel(endPointAddress);
        }
    }    
}

I get an error of course that there is no InstanceContext because I am constructing using typeof(Cback) ..

"This CreateChannel overload cannot be called on this instance of DuplexChannelFactory, as the DuplexChannelFactory was initialized with a Type and no valid InstanceContext was provided."

So I am not sure how I can go about performing this ? And for those that say read the error : yes I read the error. Now how to do that with an InstanceContext that does not exist as OperationContext.Current does not exist at this point as I am calling this method form my Hosting Process into my WCFHost.

So if I could have a nice example of how to do this - even if I must use the code example on the 2nd link (of course implementing the DuplexChannelFactory) I would greatly appreciate it.

EDIT Basically the windows Service is doing some heavy work monitoring other services, about 2 times a second it then must publish that to "Subscribed" Clients via WCF.

Community
  • 1
  • 1
Ken
  • 2,518
  • 2
  • 27
  • 35

1 Answers1

1

I think you have got very confused about how everything is wired together and are mixing concepts from the client in with the service. You haven't provided much concrete information about your scenario to go on so I'm going to provide a small example and hopefully you will be able to apply the ideas to your problem.

[ServiceContract(CallbackContract=typeof(IMyServiceCallback))]
public interface IMyService
{
    [OperationContract]
    void Register();
}

public interface IMyServiceCallback
{
    [OperationContract]
    void ReceiveData(string data);
}

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class MyService : IMyService
{
    static HashSet<IMyServiceCallback> s_allClients = new HashSet<IMyServiceCallback>();
    static object s_lockobj = new object();

    public void Register()
    {
        lock(s_lockobj)
        {
            _allClients.Add(OperationContext.Current.GetCallbackChannel<IMyServiceCallback>());
        }
    }

    public static void SendDataToClients(string data)
    {
        HashSet<IMyServiceCallback> tempSet;
        lock(s_lockobj)
        {
            tempSet = new HashSet<IMyServiceCallback>(_allClients);
        }
        foreach(IMyServiceCallback cb in tempSet)
        {
            try
            {
                cb.ReceiveData(data);
            }
            catch(Exception)
            {
                lock(s_lockobj)
                {
                    _allClients.Remove(cb);
                    cb.Abort();
                    cb.Dispose();
                }
            }
        }
    }
}

In your OnEvent method, you would call something similar to this inside your event method.

MyService.SendDataToClients(mydata);

This uses static data to store the list of clients. If you wanted to do something like segment your clients for different endpoints, you would need to do something different. There is a potential out of order message and scaling problem with this code if your OnEvent method can be called again while the previous call hasn't completed. For example, if you receive 2 messages, the first being large and the second being small, you could potentially send the second smaller message to clients later in the HashSet iteration order before they have been sent the first message. Also this won't scaled to a large number of clients as you could block timing out on one client holding up messages being sent to other clients. You could use something similar to Task's to dispatch multiple message deliveries. If this needs to scale, I would suggest looking at Reactive Extensions for .Net

MattC
  • 373
  • 1
  • 6
  • yes that last piece of code and paragraph might be what I am looking for. If I use sessions Will making the SendDataMethod method static cause any threading issues? If it is not a static method then I would need more code to find the InstanceContext of the ServiceHost that is created when calling it (is that correct?). I was confused because the examples I have come across like in the link suggest to create a client or channel in my hosting service of course thats not what I wanted to do but I need to 'just' make it work. – Ken Oct 13 '15 at 16:38
  • One more quick Question: If I use PerCall, or perSession Do I still use (MyService)serviceHost.SingletonInstance; – Ken Oct 13 '15 at 16:45
  • The access to _allClients will need to be protected by a lock. In the SendDataToClients method, I copied the clients into a new HashSet to avoid holding a lock for an extended period. I also didn't copy it inside of a lock. PerCall and PerSession means you have more than one instance so you can't use SingletonInstance. If there's no need to have multiple instances, I would use a singleton as it uses less memory. But you need to be aware of the thread safety of your own code. I'll modify the answer in a bit to put the correct locks in place and to use a static method instead. – MattC Oct 14 '15 at 00:36
  • -yes I understand the reason for the copy -short lived lock, not sure why you did not copy inside the lock though. As for the SingletonInstance - my reading seems to indicate requests will be queued not really multithreaded. In the case of non-singleton - I would need to get the service instance created from the request (how I do not know yet).. - I guess I could start with the singleton and make something work and go research the other after that. – Ken Oct 14 '15 at 13:19
  • 1
    The requests will be queued depending on the ConcurrencyMode. If you set ConcurrencyMode = ConcurrencyMode.Multiple, then there's no queuing and a method on a single instance can be entered from multiple requests at the same time. – MattC Oct 15 '15 at 21:39