Abhay Sheth
Abhay Sheth

Reputation: 3

How to send message through WCF duplex service without crash application?

I have a WCF service for communicating between an Admin PC and multiple Client PCs.

Here the WCF service will be hosted on live while Admin PC and Client PC using application which is build in WPF. WCF is working as duplex service for handle events and broadcast events to other users.

Like if admin will send message to service then it will be broadcast to all clients while when client send message to service then it will also broadcast to all other users.

when multiple messages are sending to service then it's crashing the application. Here I have put my code below so please check it and suggest me for solve this issue.

WCF Service Code

IBroadcastorService1 Interface:

[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = 
typeof(IBroadcastorCallBack1))]
public interface IBroadcastorService1
{
    [OperationContract(IsOneWay = true)]
    void RegisterClient(string clientName);

    [OperationContract(IsOneWay = true)]
    void NotifyServer(EventDataType eventData);
}
public interface IBroadcastorCallBack1
{
    [OperationContract(IsOneWay = true)]
    void BroadcastToClient(EventDataType eventData);
}
[DataContract]
public class EventDataType
{
    [DataMember]
    public string ClientName { get; set; }

    [DataMember]
    public string EventMessage { get; set; }
}

BroadcastorService.svc.cs contains below code :

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
                 ConcurrencyMode = ConcurrencyMode.Multiple)]
public class BroadcastorService : IBroadcastorService1
{
    private static Dictionary<string, IBroadcastorCallBack1> clients = new Dictionary<string, IBroadcastorCallBack1>();
    private static object locker = new object();

    public void RegisterClient(string clientName)
    {
        if (clientName != null && clientName != "")
        {
            try
            {
                IBroadcastorCallBack1 callback = OperationContext.Current.GetCallbackChannel<IBroadcastorCallBack1>();
                lock (locker)
                {
                    //remove the old client
                    if (clients.Keys.Contains(clientName))
                        clients.Remove(clientName);
                    clients.Add(clientName, callback);
                }
            }
            catch (Exception ex)
            {
            }
        }
    }

    public void NotifyServer(EventDataType eventData)
    {
        lock (locker)
        {
            var inactiveClients = new List<string>();
            foreach (var client in clients)
            {
                if (client.Key != eventData.ClientName)
                {
                    try
                    {
                        client.Value.BroadcastToClient(eventData);
                    }
                    catch (Exception ex)
                    {
                        inactiveClients.Add(client.Key);
                    }
                }
            }

            if (inactiveClients.Count > 0)
            {
                foreach (var client in inactiveClients)
                {
                    clients.Remove(client);
                }
            }
        }
    }
}

}

And web.config file is like :

<services>
  <service behaviorConfiguration="Service" name="WcfMultipleCallBacks.BroadcastorService">
    <endpoint address="" binding="wsDualHttpBinding" contract="WcfMultipleCallBacks.IBroadcastorService1" />
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
  </service>
</services>

WPF Admin application code. I have create a class for handle events relate to WCF service reference : BroadcastorCallback.cs class :

public class BroadcastorCallback : IBroadcastorService1Callback
{
        private System.Threading.SynchronizationContext synContext = AsyncOperationManager.SynchronizationContext;
        private EventHandler _broadcastorCallBackHandler;


        //SetHandler, is used to set the callback handler for the client.
        public void SetHandler(EventHandler handler)
        {
            this._broadcastorCallBackHandler = handler;
        }

//BroadcastToClient, is used to allow the service to call the client. 
        //When other clients send an event notification to the service, the service will connect to this client 
        //through the callback channel, then call this method to notify this client the event.
        public void BroadcastToClient(EventDataType eventData)
        {
            synContext.Post(new System.Threading.SendOrPostCallback(OnBroadcast), eventData);
        }

        //OnBroadcast, is the connection between the client callback handler, which is set in the first method, 
        //and the actual service call, which will be invoked by the service through the second method. 
        //When the service calls the second method, BroadcastToClient, to notify a event, the call will be delegated to 
        //this method, OnBroadcast, and then the same call will be delegated to the client callback handler.
        public void OnBroadcast(object eventData)
        {
            this._broadcastorCallBackHandler.Invoke(eventData, null);
        }

   }
}

While MainWindow.cs contain code like :

private ServiceReference1.BroadcastorService1Client _client;
public MainWindow()
        {
            InitializeComponent();
            RegisterClient();
        }
private delegate void HandleBroadcastCallback(object sender, EventArgs e);
        public void HandleBroadcast(object sender, EventArgs e)
        {
            try
            {
                var eventData = (ServiceReference1.EventDataType)sender;
                if (this.txtEventMessages.Text != "")
                    this.txtEventMessages.Text += "\r\n";
                this.txtEventMessages.Text += string.Format("{0} (from {1})",
                    eventData.EventMessage, eventData.ClientName);
            }
            catch (Exception ex)
            {
            }
        }

private void RegisterClient()
        {
            if ((this._client != null))
            {
                this._client.Abort();
                this._client = null;
            }

            BroadcastorCallback cb = new BroadcastorCallback();
            cb.SetHandler(this.HandleBroadcast);

            System.ServiceModel.InstanceContext context = new System.ServiceModel.InstanceContext(cb);
            this._client = new ServiceReference1.BroadcastorService1Client(context);

            //this._client.RegisterClient(this.txtClientName.Text);
            this._client.RegisterClient("Harry Potter");
        }

private void btnSendEvent_Click(object sender, RoutedEventArgs e)
        {
this._client.NotifyServer(
                       new ServiceReference1.EventDataType()
                       {
                           ClientName = "Harry Potter",
                           //EventMessage = this.txtEventMessage.Text
                           EventMessage = count.ToString()
                       });
          }
        }

This code is working perfect when there is message sending is not too much speedy. But when message communication is too much speedy then it's crashing wpf application.

For testing purpose when I am applying While loop on SendEvent then it's crashing WPF application like.

private bool isRun = false;
private void btnSendEvent_Click(object sender, RoutedEventArgs e)
        {
isRun = true;
while(isRun = true)
{
this._client.NotifyServer(
                       new ServiceReference1.EventDataType()
                       {
                           ClientName = "Harry Potter",
                           //EventMessage = this.txtEventMessage.Text
                           EventMessage = count.ToString()
                       });
}
          }
        }

I check with setting debugger but don't know which part is making my application crash and why it's happening in speedy communication.

Upvotes: 0

Views: 588

Answers (1)

user585968
user585968

Reputation:


This is a summary of my comments above the question.


I think there are a few problems in your code that arrise when you are calling the service under load.

Your service is marked as a singleton, multithreaded-aware service by this WCF ServiceBehavior declaration.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, // singleton
                 ConcurrencyMode = ConcurrencyMode.Multiple)] // thread-safe(?)
public class BroadcastorService : IBroadcastorService1 { ... }

InstanceContextMode.Single is straight-forward as WCF sets this up for you with nothing that you really need to do however ConcurrencyMode.Multiple is a declaration on your part that you can accept multiple concurrent calls by multiple threads. You declare that you accept all responsibility and you don't hold WCF accountable. WCF is trusting you that you won't shoot yourself in the foot.

Normally it is better to let WCF determine how and when calls from clients make their way into your service code. By default, WCF serialises all calls invoking methods on your service one at a time, making your service thread-safe without developers having to do anything with scary lock()s. When used with InstanceContextMode it can lead to better scalability of your service host.

Consider too that the first thing each of your WCF service methods do is to perform a lock() on the whole hard-coded singleton, you are not getting any benefit from ConcurrencyMode.Multiple. You may as well use ConcurrencyMode.Single, remove all of the lock()s and let WCF do all the serialization of method calls for you. Much safer than manually making your service thread-safe. Plus if you ever want to remove the singleton nature of your service and use say InstanceContextMode.PerCall or InstanceContextMode.PerSession, it is argbuably a one-line change.

When your app is under load, the fact that your service is marked as:

  • "I can handle any number of concurrent thread calls" :P and
  • "Let all threads execute against the same service object" :P

...can result in a very risky service. By making the above suggested changes you effectively throttle the number of concurrent calls from all your clients thus making the service more stable.

Crashes, Exception Handling and Debugging Tips

You mentioned that your app is crashing but you didn't say what the error was. Looking at your code I can see alot of:

try
{
    // something here
}
catch (Exception ex)
{
}

Generally you want to avoid doing this sort of thing because you are telling .NET that you want to silently catch all exceptions. You as the developer aren't even being informed of possibly nasty bugs in your code. Catching all exceptions is rather naughty as you really want to just catch the ones you are expecting and perhaps setup a unhandled exception handler for everything else elsewhere in your code that simply displays a fatal message to the user before shutting down the app somewhat gracefully.

To improve your debugging, make sure you run your app in the Visual Studio debugger.

From the Debug menu, choose Debug.Windows.Exception Settings.

In the tool window that appears, tick the box Common Language Runtime Exceptions. This tells VS that you want to be informed of all CLR exceptions. (later you can go in and just pick the ones you want)

Now whenever an exception is thrown, the debugger will stop and place the cursor on the offending line. Note: at this point this is what is known as a first-chance exception as the debugger stops immediately on the line. Let's imagine a TimeoutException was thrown. It's not necessarily an error as you might say have a catch (TimeoutException) somewhere. It has not yet progressed to the first catch() (if any) block so don't be alarmed. If you press F5 (or Debug.Continue menu), then the debugger will resume the app stopping at your catch(TimeoutException). Now had you not ticked the box in Debug Settings, the debugger would have just gone straight to your catch(TimeoutException) without issuing a first-chance notification. The problem is now you don't know where the error occurred without looking at the call stack in the Exception object.

Proxy clients

Though probably not an immediate concern, I notice too that your clients are creating a WCF proxy and storing it in a field of your app's MainWindow class. The thing about proxies is that they are subject to breaking after a period of time and WCF is no exception. Generally they represent a network connections. Networks come and go. Connections can simply timeout if idle and be closed by the server. The client won't know about it until it goes to invoke it by which time it will be too late. You'll get an xxxException and the proxy will be marked as faulted meaning that it can't be used again. You'll need to make another one.

For this reason, it's generally better to create a proxy at the point prior to making your first call, then getting rid of it (you should Dispose() it) once the current batch of calls are complete. That, or build into your application that it can handle WCF faults and re-create the proxy when it needs to.

Now, depending on what WCF binding you are using, timeouts vary, it may be 1, 5 or 10 mins.

Again this is just a FYI, I don't think it is happending here but you never know.

Don't Let the UI Thread Call the WCF Service

OP:

When signal sending is start from Admin side to service then I am not able to do any thing on admin screen. Even I can not minimize, maximize or close that screen

Your Admin client is freezing because you are calling the WCF service from your btnSendEvent_Click handler. The UI won't do anything until that method returns. This is the nature of all UI. No UI is multithreaded. The fact that your click handler is performing an expensive and timely network call is just going to make your UI more notiecably unresponsive. Perhaps you need to call it inside a worker thread that the BackgroundWorker component provides (novice easier) or asynchronously via async/await (better).

OP:

Thank you very much for your support. Right now I have use BackgroundWorker in Admin side application and apply changes on WCF service as per your instruction. Now it's sending signal smoothly. without crash and freezing of Admin Side application.

I am glad to hear that my suggestion helped solved the issue.

Tell me more

I highly recommend this excellent WCF bible in either book/Kindle form:

enter image description here

Upvotes: 1

Related Questions