[英]How to send message through WCF duplex service without crash application?
我有一个WCF服务,用于在管理PC和多台 客户端PC之间进行通信。
在这里,WCF服务将实时托管,而Admin PC和Client PC将使用WPF中内置的应用程序。 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()
});
}
}
}
我检查了调试器的设置,但不知道是哪部分使我的应用程序崩溃以及为什么它在快速通信中发生。
这是我对问题的评论的摘要。
我认为在您的负载下调用服务时,代码中会出现一些问题。
您的服务标记为这个WCF一个单 , 多线程感知服务ServiceBehavior
声明。
[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.PerCall
或InstanceContextMode.PerSession
,这很可能是单行更改。
当您的应用加载时,您的服务被标记为:
...可能会导致非常危险的服务。 通过进行上述建议的更改,您可以有效地限制来自所有客户端的并发呼叫数,从而使服务更加稳定 。
您提到您的应用程序崩溃了,但您没有说明错误是什么。 查看您的代码,我可以看到很多:
try
{
// something here
}
catch (Exception ex)
{
}
通常,您要避免做这种事情,因为您是在告诉.NET您要静默捕获所有异常。 作为开发人员,您甚至都没有意识到代码中可能存在讨厌的错误。 捕获所有异常是相当调皮 ,你真的想正好赶上您所期待的那些,或许设置了一切别的地方在你的代码中未处理的异常处理程序 ,只显示一个致命的消息给用户关闭应用程序有点优雅前。
要改善调试,请确保在Visual Studio调试器中运行应用程序。
从“ 调试”菜单中,选择“ Debug.Windows.Exception设置” 。
在出现的工具窗口中,选中“ 公共语言运行时例外 ”框。 这告诉VS,您想被告知所有 CLR异常。 (稍后您可以进入,然后选择您想要的人)
现在,每当引发异常时,调试器就会停止并将光标放在有问题的行上。 注意:这是所谓的“ 第一次机会异常”,因为调试器立即停止在线。 让我们想象一下抛出了TimeoutException
。 这不一定是错误,因为您可能会说某个地方有一个catch (TimeoutException)
。 它尚未进行到第一个catch()
(如果有的话)块,因此请勿惊慌。 如果按F5 (或Debug.Continue菜单),则调试器将继续在catch(TimeoutException)
处停止应用程序。 现在,如果您没有在“调试设置”中打勾,则调试器将直接进入您的catch(TimeoutException)
而不会发出优先机会通知。 现在的问题是,如果不查看Exception
对象中的调用堆栈,就不知道错误发生在哪里。
尽管可能不是紧迫的问题,但我也注意到您的客户端正在创建WCF代理并将其存储在应用程序的MainWindow
类的字段中。 关于代理的事情是,它们在一段时间后会破裂,WCF也不例外。 通常,它们代表网络连接。 网络来来往往。 如果空闲 ,连接可以简单地超时并被服务器关闭 。 直到它去调用它之前,客户端都不会知道它。 您将得到一个xxxException
并且该代理将被标记为错误 ,这意味着它不能再次使用 。 您需要再做一个。
因此,通常最好在进行首次调用之前在该位置创建一个代理,然后在完成当前一批调用后将其删除(您应该使用Dispose()
)。 这样,或者在您的应用程序中内置它可以处理WCF错误并在需要时重新创建代理。
现在,根据您使用的WCF绑定,超时可能有所不同,可能是1分钟,5分钟或10分钟。
同样,这只是一个仅供参考,我不认为这是在这里发生,但您永远都不会知道。
OP:
从管理员端开始向服务发送信号时,我无法在管理员屏幕上执行任何操作。 甚至我也无法最小化,最大化或关闭该屏幕
您的Admin客户端处于冻结状态,因为您正在从btnSendEvent_Click
处理程序调用WCF服务。 在该方法返回之前,UI不会执行任何操作。 这是所有UI的本质。 没有 UI是多线程的。 您的点击处理程序正在执行昂贵且及时的网络呼叫这一事实只会使您的UI明显变得无响应。 也许您需要在BackgroundWorker
组件提供的工作线程(更容易完成更改)中调用它,或者通过async/await
(更好)进行async/await
调用。
OP:
非常感谢您的支持。 现在,我已在管理端应用程序中使用
BackgroundWorker
,并按照您的指示将更改应用于WCF服务。 现在它正在 平稳地 发送信号。 不会崩溃和冻结管理端应用程序。
我很高兴听到我的建议帮助解决了这个问题。
我强烈建议以书籍/ Kindle形式使用这本出色的WCF圣经:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.