如何在没有崩溃应用程序的情况下通过 WCF 双工服务发送消息?
How to send message through WCF duplex service without crash application?
我有一个 WCF 服务,用于在 Admin PC 和 multiple Client PC 之间进行通信.
这里 WCF 服务将实时托管,而管理员 PC 和客户端 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 管理应用程序代码。
我已经创建了一个 class 用于处理与 WCF 服务引用相关的事件:
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);
}
}
}
而 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
声明标记为 单例、multithreaded-aware 服务。
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, // singleton
ConcurrencyMode = ConcurrencyMode.Multiple)] // thread-safe(?)
public class BroadcastorService : IBroadcastorService1 { ... }
InstanceContextMode.Single
是 straight-forward,因为 WCF 为您设置了这个,您不需要做任何事情 然而 ConcurrencyMode.Multiple
是 声明 您可以接受多个线程的多个并发调用。您声明您接受所有责任并且您不追究 WCF 的责任。 WCF 相信你不会搬起石头砸自己的脚。
通常最好让 WCF 确定来自客户端的调用如何以及何时进入您的服务代码。默认情况下,WCF 序列化所有调用您服务的方法一次一个,使您的服务thread-safe 开发人员无需对可怕的 lock()
做任何事情。当与 InstanceContextMode
一起使用时,它可以为您的服务主机带来更好的 可扩展性 。
还请考虑,您的每个 WCF 服务方法所做的第一件事是对整个 hard-coded 单例执行 lock()
,您不会从 ConcurrencyMode.Multiple
中获得任何好处。您也可以使用 ConcurrencyMode.Single
,删除所有 lock()
并让 WCF 为您完成所有方法调用的序列化。 比手动创建服务 thread-safe 更安全。另外,如果您想删除服务的单例性质并使用 InstanceContextMode.PerCall
或 InstanceContextMode.PerSession
,这无疑是一个 one-line 更改。
当您的应用处于加载状态时,您的服务被标记为:
- "I can handle any number of concurrent thread calls" :P 和
- "Let all threads execute against the same service object" :P
...可能会导致 风险很大 的服务。通过进行上述建议的更改,您可以有效地节流所有客户端的并发调用数量,从而使服务更加稳定.
崩溃、异常处理和调试技巧
你提到你的应用程序崩溃了,但你没有说明错误是什么。查看您的代码,我可以看到很多:
try
{
// something here
}
catch (Exception ex)
{
}
通常您希望避免执行此类操作,因为您是在告诉 .NET 您希望默默地捕获所有异常。作为开发人员,您甚至不会被告知您的代码中可能存在严重的错误。捕获所有异常相当 顽皮 因为你真的只想捕获你期望的那些并且可能为您的代码只是在 优雅地 关闭应用程序之前向用户显示 致命消息 。
要改进调试,请确保在 Visual Studio 调试器中 运行 您的应用程序。
从调试菜单中,选择Debug.Windows.Exception设置。
在出现的工具 window 中,勾选方框 公共语言运行时异常 。这告诉 VS 你想被告知 all CLR 异常。 (稍后你可以进去挑选你想要的)
现在每当抛出异常时,调试器都会停止并将光标放在有问题的行上。 注意:此时这就是所谓的first-chance异常,因为调试器会立即停止。让我们想象一下 TimeoutException
被抛出。这不一定是错误,因为您可能会说某处有 catch (TimeoutException)
。它还没有进展到第一个 catch()
(如果有的话)块,所以不要惊慌。如果您按 F5(或 Debug.Continue 菜单),调试器将恢复在您的 catch(TimeoutException)
处停止的应用程序。现在如果您没有勾选调试设置中的框,调试器将直接进入您的 catch(TimeoutException)
而不会发出 first-chance 通知。现在的问题是,如果不查看 Exception
对象中的调用堆栈,您将不知道错误发生在何处。
代理客户端
虽然可能不是一个紧迫的问题,但我也注意到您的客户正在创建一个 WCF 代理并将其存储在您应用的 MainWindow
class 的字段中。关于代理的事情是它们在一段时间后会被破坏,WCF 也不例外。通常它们代表一个网络连接。网络来来去去。连接可以简单地 timeout if idle 并且被服务器closed。客户端在调用它之前不会知道它,到那时就太晚了。您将得到一个 xxxException
并且代理将被标记为 故障 意味着它 不能再次使用 。你需要再做一个。
出于这个原因,通常最好在第一次调用之前创建一个代理然后在当前批次调用完成后摆脱它(你应该 Dispose()
它)。那个,或者构建到你的应用程序中,它可以处理 WCF 错误和 re-create 需要时代理。
现在,根据您使用的 WCF 绑定,超时会有所不同,可能是 1、5 或 10 分钟。
同样,这只是一个仅供参考,我不认为它会发生在这里,但你永远不会知道。
不要让 UI 线程调用 WCF 服务
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
您的管理客户端正在冻结,因为您正在从 btnSendEvent_Click
处理程序调用 WCF 服务。 UI 在那个方法 returns 之前不会做任何事情。这是所有UI的本质。 否 UI 是多线程的。您的点击处理程序正在执行昂贵且及时的网络调用这一事实只会使您的 UI 更加明显地无响应。也许您需要在 BackgroundWorker
组件提供的工作线程中调用它(新手更容易)或通过 async/await
异步调用(更好)。
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.
很高兴听到我的建议帮助解决了问题。
告诉我更多
我强烈推荐 book/Kindle 两种形式的这本优秀的 WCF 圣经:
- Lowy, Juval, "Programming WCF Services: Mastering WCF and the Azure AppFabric Service Bus, 3rd Edition", O'Reilly Media, 2010
我有一个 WCF 服务,用于在 Admin PC 和 multiple Client PC 之间进行通信.
这里 WCF 服务将实时托管,而管理员 PC 和客户端 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 管理应用程序代码。 我已经创建了一个 class 用于处理与 WCF 服务引用相关的事件: 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);
}
}
}
而 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
声明标记为 单例、multithreaded-aware 服务。
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, // singleton
ConcurrencyMode = ConcurrencyMode.Multiple)] // thread-safe(?)
public class BroadcastorService : IBroadcastorService1 { ... }
InstanceContextMode.Single
是 straight-forward,因为 WCF 为您设置了这个,您不需要做任何事情 然而 ConcurrencyMode.Multiple
是 声明 您可以接受多个线程的多个并发调用。您声明您接受所有责任并且您不追究 WCF 的责任。 WCF 相信你不会搬起石头砸自己的脚。
通常最好让 WCF 确定来自客户端的调用如何以及何时进入您的服务代码。默认情况下,WCF 序列化所有调用您服务的方法一次一个,使您的服务thread-safe 开发人员无需对可怕的 lock()
做任何事情。当与 InstanceContextMode
一起使用时,它可以为您的服务主机带来更好的 可扩展性 。
还请考虑,您的每个 WCF 服务方法所做的第一件事是对整个 hard-coded 单例执行 lock()
,您不会从 ConcurrencyMode.Multiple
中获得任何好处。您也可以使用 ConcurrencyMode.Single
,删除所有 lock()
并让 WCF 为您完成所有方法调用的序列化。 比手动创建服务 thread-safe 更安全。另外,如果您想删除服务的单例性质并使用 InstanceContextMode.PerCall
或 InstanceContextMode.PerSession
,这无疑是一个 one-line 更改。
当您的应用处于加载状态时,您的服务被标记为:
- "I can handle any number of concurrent thread calls" :P 和
- "Let all threads execute against the same service object" :P
...可能会导致 风险很大 的服务。通过进行上述建议的更改,您可以有效地节流所有客户端的并发调用数量,从而使服务更加稳定.
崩溃、异常处理和调试技巧
你提到你的应用程序崩溃了,但你没有说明错误是什么。查看您的代码,我可以看到很多:
try
{
// something here
}
catch (Exception ex)
{
}
通常您希望避免执行此类操作,因为您是在告诉 .NET 您希望默默地捕获所有异常。作为开发人员,您甚至不会被告知您的代码中可能存在严重的错误。捕获所有异常相当 顽皮 因为你真的只想捕获你期望的那些并且可能为您的代码只是在 优雅地 关闭应用程序之前向用户显示 致命消息 。
要改进调试,请确保在 Visual Studio 调试器中 运行 您的应用程序。
从调试菜单中,选择Debug.Windows.Exception设置。
在出现的工具 window 中,勾选方框 公共语言运行时异常 。这告诉 VS 你想被告知 all CLR 异常。 (稍后你可以进去挑选你想要的)
现在每当抛出异常时,调试器都会停止并将光标放在有问题的行上。 注意:此时这就是所谓的first-chance异常,因为调试器会立即停止。让我们想象一下 TimeoutException
被抛出。这不一定是错误,因为您可能会说某处有 catch (TimeoutException)
。它还没有进展到第一个 catch()
(如果有的话)块,所以不要惊慌。如果您按 F5(或 Debug.Continue 菜单),调试器将恢复在您的 catch(TimeoutException)
处停止的应用程序。现在如果您没有勾选调试设置中的框,调试器将直接进入您的 catch(TimeoutException)
而不会发出 first-chance 通知。现在的问题是,如果不查看 Exception
对象中的调用堆栈,您将不知道错误发生在何处。
代理客户端
虽然可能不是一个紧迫的问题,但我也注意到您的客户正在创建一个 WCF 代理并将其存储在您应用的 MainWindow
class 的字段中。关于代理的事情是它们在一段时间后会被破坏,WCF 也不例外。通常它们代表一个网络连接。网络来来去去。连接可以简单地 timeout if idle 并且被服务器closed。客户端在调用它之前不会知道它,到那时就太晚了。您将得到一个 xxxException
并且代理将被标记为 故障 意味着它 不能再次使用 。你需要再做一个。
出于这个原因,通常最好在第一次调用之前创建一个代理然后在当前批次调用完成后摆脱它(你应该 Dispose()
它)。那个,或者构建到你的应用程序中,它可以处理 WCF 错误和 re-create 需要时代理。
现在,根据您使用的 WCF 绑定,超时会有所不同,可能是 1、5 或 10 分钟。
同样,这只是一个仅供参考,我不认为它会发生在这里,但你永远不会知道。
不要让 UI 线程调用 WCF 服务
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
您的管理客户端正在冻结,因为您正在从 btnSendEvent_Click
处理程序调用 WCF 服务。 UI 在那个方法 returns 之前不会做任何事情。这是所有UI的本质。 否 UI 是多线程的。您的点击处理程序正在执行昂贵且及时的网络调用这一事实只会使您的 UI 更加明显地无响应。也许您需要在 BackgroundWorker
组件提供的工作线程中调用它(新手更容易)或通过 async/await
异步调用(更好)。
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.
很高兴听到我的建议帮助解决了问题。
告诉我更多
我强烈推荐 book/Kindle 两种形式的这本优秀的 WCF 圣经:
- Lowy, Juval, "Programming WCF Services: Mastering WCF and the Azure AppFabric Service Bus, 3rd Edition", O'Reilly Media, 2010