如何在没有崩溃应用程序的情况下通过WCF双工服务发送消息?

时间:2019-06-22 09:52:47

标签: c# wpf wcf

我有一个WCF服务,用于在管理PC 多个 客户端PC 之间进行通信。

这里,WCF服务将在Admin PC和Client PC上使用WPF内置的应用程序实时托管。 WCF作为 duplex 服务,用于处理事件并向其他用户广播事件。

例如,如果管理员将消息发送给服务,则它将广播给所有客户端,而当客户端将消息发送给服务时,也会广播给所有其他用户。

多个 消息正在发送服务时,则会崩溃该应用程序 。这里我将代码放在下面,因此请检查一下并建议我解决此问题。

WCF服务代码

IBroadcastorService1界面:

[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 包含以下代码:

[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);
                }
            }
        }
    }
}

}

和web.config文件类似:

<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应用程序代码。 我为与WCF服务参考相关的句柄事件创建了一个类: BroadcastorCallback.cs类:

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);
        }

   }
}

MainWindow.cs包含如下代码:

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()
                       });
          }
        }

当消息发送速度不太快时,此代码可以完美地工作。但是,当消息通信速度太快时,它就会使wpf应用程序崩溃。

出于测试目的,当我在SendEvent上应用While循环时,它使WPF应用程序崩溃。

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()
                       });
}
          }
        }

我检查了设置调试器,但不知道是哪个部分导致我的应用程序崩溃以及为什么在快速通信中会发生这种情况。

1 个答案:

答案 0 :(得分:0)


这是我对问题的评论摘要。


我认为当您在负载下调用服务时,代码中会出现一些问题。

此WCF ServiceBehavior声明将您的服务标记为 singleton 多线程感知服务。

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

InstanceContextMode.Single简单明了,因为WCF为您设置了此功能,而您实际上并不需要做任何事情然而 ConcurrencyMode.Multiple声明您可以接受多个线程进行的多个并发调用。您声明自己承担所有责任,并且不对WCF负责。 WCF相信您不会踢脚。

通常,最好让WCF确定来自客户端的呼叫如何以及何时进入您的服务代码。默认情况下,WCF 序列化所有一次调用服务上的调用方法一次,从而使服务具有线程安全,而开发人员无需执行任何操作lock()令人恐惧。与InstanceContextMode一起使用时,可以提高服务主机的可扩展性

还要考虑到,每个WCF服务方法要做的第一件事就是对整个硬编码单例执行lock(),您不会从ConcurrencyMode.Multiple中受益。您也可以使用ConcurrencyMode.Single,删除所有lock(),然后让WCF为您完成方法调用的所有序列化。与手动使服务成为线程安全的相比,更安全。另外,如果您想删除服务的单例性质并使用InstanceContextMode.PerCallInstanceContextMode.PerSession这样的话,那无疑是单行更改。

当您的应用加载时,您的服务被标记为:

  • “我可以处理任意数量的并发线程调用” :P和
  • “让所有线程针对同一个服务对象执行” :P

...可能会导致非常风险的服务。通过进行上述建议的更改,您可以有效地节制来自所有客户端的并发呼叫次数,从而使服务更稳定

崩溃,异常处理和调试提示

您提到您的应用程序崩溃了,但您没有说明错误是什么。查看您的代码,我可以看到很多:

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

通常,您要避免做这种事情,因为您是在告诉.NET您要静默捕获所有异常。作为开发人员,您甚至都没有意识到代码中可能存在讨厌的错误。捕获所有异常是顽皮的,因为您确实想捕获期望的异常,并且可能为代码中其他地方的所有内容设置了一个未处理的异常处理程序,该异常处理只是显示一个在向用户发出致命消息之前,先优雅地关闭该应用。

要改善调试,请确保在Visual Studio调试器中运行应用程序。

Debug 菜单中,选择 Debug.Windows.Exception Settings

在显示的工具窗口中,选中公共语言运行时例外框。这告诉VS,您希望收到所有 CLR异常的通知。 (稍后您可以进去,然后选择想要的东西)

现在,每当引发异常时,调试器将停止并将光标放在有问题的行上。 注意:这是所谓的 first-chance例外,因为调试器会立即停止在线。假设抛出了TimeoutException。不一定要出错,因为您可能会说在某个地方有一个catch (TimeoutException)。它尚未进行到第一个catch()(如果有)块,因此请不要惊慌。如果按 F5 (或 Debug.Continue 菜单),则调试器将恢复在catch(TimeoutException)停止的应用程序。现在,如果您没有在“调试设置”中打勾,则调试器将直接进入您的catch(TimeoutException)而不会发出优先机会通知。现在的问题是,如果不查看Exception对象中的调用堆栈,就不知道错误发生在哪里。

代理客户端

尽管可能不是紧迫的问题,但我也注意到您的客户端正在创建WCF代理并将其存储在应用程序的MainWindow类的字段中。关于代理的事情是,它们在一段时间后会破裂,WCF也不例外。通常,它们代表网络连接。网络来来往往。如果空闲,连接可以超时,并被服务器关闭。直到它去调用它之前,客户端都不会知道它。您将得到一个xxxException,并且该代理将被标记为故障,这意味着它无法再次使用。您需要再做一个。

由于这个原因,通常最好在进行第一次呼叫之前在该点创建一个代理,然后在当前一批呼叫完成后将其删除(您应该Dispose())。这样,或者在您的应用程序中内置它可以处理WCF错误并在需要时重新创建代理。

现在,根据您使用的WCF绑定的不同,超时可能会有所不同,可能是1分钟,5分钟或10分钟。

再说一次,仅供参考,我不认为这是发生在这里,但是你永远都不知道。

不要让UI线程调用WCF服务

OP:

  

从管理员端开始向服务发送信号时,我无法在管理员屏幕上执行任何操作。甚至我也无法最小化,最大化或关闭该屏幕

您的Admin客户端处于冻结状态,因为您正在从btnSendEvent_Click处理程序调用WCF服务。在该方法返回之前,UI不会执行任何操作。这是所有UI的本质。 用户界面是多线程的。您的点击处理程序正在执行昂贵且及时的网络呼叫这一事实只会使您的UI明显变得无响应。也许您需要在BackgroundWorker组件提供的工作线程中调用它(更容易新颖),或者通过async/await异步地调用它(更好)。

OP:

  

非常感谢您的支持。现在,我已经在管理端应用程序中使用了BackgroundWorker,并按照您的指示将更改应用于WCF服务。现在,它发送信号平稳没有崩溃和冻结的管理端应用程序。

我很高兴听到我的建议帮助解决了这个问题。

告诉我更多

我强烈推荐这本出色的WCF圣经,无论是书籍还是Kindle形式:

enter image description here