防止 WCF 双工回调服务出现死锁问题
Posted
技术标签:
【中文标题】防止 WCF 双工回调服务出现死锁问题【英文标题】:Prevent deadlock issue with WCF duplex callback service 【发布时间】:2013-01-01 20:04:36 【问题描述】:我有一个自托管 wcf 双工回调服务的问题。我收到带有消息的InvalidOperationException
:
此操作会因为无法收到回复而死锁 直到当前消息完成处理。如果你想允许 乱序消息处理,指定 Reentrant 的 ConcurrencyMode 或 CallbackBehaviorAttribute 上的多个。
这是我的服务行为:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = true)]
这是我的服务合同:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IClientCallback))]
[ServiceContract]
public interface IClientToService
[OperationContract(IsOneWay = false)]
LVSSStatus GetLvssStatus();
[OperationContract(IsOneWay = true)]
void PickSpecimen(long trackingNumber, int destCode);
[OperationContract(IsOneWay = true)]
void CancelCurrentPickTransaction();
这是我的回调接口:
public interface ILvssClientCallback
[OperationContract(IsOneWay = true)]
void SendClientCallback(LvssCallbackMessage callbackMessage);
[OperationContract(IsOneWay = false)]
List<SpecimenTemplateDescriptor> GetTemplateDescriptorList(DrawerLayout drawerLayout);
[OperationContract(IsOneWay = false)]
SpecimenTemplate SelectSpecimenTemplate(string templateName, int version);
[OperationContract]
void SpecimenStoredInContainer(string containerID, bool isValidRackID, int rackRow, int rackCol, int deckRow, int deckCol,
int drawerRow, int drawerCol, long trackingNumber, RobotErrors robotError);
[OperationContract]
void LvssRobotStatusChange(LVSSStatus status);
我了解InvalidOperationException
是在客户端调用回调操作时引起的,该服务已被锁定以处理当前操作。因此,发生了死锁。
我尝试将我的 ConcurrencyMode 更改为多个,并将 UseSynchronizationContext 更改为 false。
我的服务仍然存在两个问题:
首先:当快速调用GetLvssStatus()
(通过快速单击 UI 按钮)时,以下服务操作会冻结我的客户端 wpf 应用程序。该方法不是一种方式,而是从服务同步返回一个枚举类型给客户端。
[OperationContract(IsOneWay = false)]
LVSSStatus GetLvssStatus();
* 在这种情况下,是什么导致我的 wpf 应用程序冻结? * 我可以做些什么来防止应用程序冻结? 如果我使用 backgroundworker 线程作为异步调用,应用程序不会冻结。我真的需要这种方法来同步工作。
第二个:当我将回调方法 LvssRobotStatusChange 分配给 IsOneWay = true
时,我得到一个 ObjectDisposedException:无法访问已处置的对象。对象名称:'System.ServiceModel.Channels.ServiceChannel'
。
[OperationContract(IsOneWay = true)]
void LvssRobotStatusChange(LVSSStatus status);
* 是什么导致了这个 ObjectDisposedException? * 在这种情况下可以省略 IsOneWay 赋值吗?在这种情况下省略 IsOneWay 允许回调完成而没有任何异常。
* 这些问题可能是由于缺少线程安全代码造成的吗? * 如果是这样,使 ConcurrencyMode.Multiple 服务行为线程安全的最佳做法是什么?
非常感谢您对这些问题的任何帮助。
* 首次编辑 关于创建我的双工频道的更多信息。我的 wpf 视图模型创建了一个代理对象,负责处理我的频道的创建。到目前为止,当服务尝试使用回调对象时,在客户端的新线程上设置我的频道的任何尝试都会导致 ObjectDisposedException。
* 第二次编辑 我相信如果我可以使用 void 方法获取操作合同来设置 IsOneWay = true,我的服务应该可以工作。在可重入并发的情况下,主通道线程应该让这些方法通过而不管任何锁定。 这是我的回调接口:
public interface ILvssClientCallback
[OperationContract(IsOneWay = true)]
void SendClientCallback(LvssCallbackMessage callbackMessage);
[OperationContract]
List<SpecimenTemplateDescriptor> GetTemplateDescriptorList(DrawerLayout drawerLayout);
[OperationContract]
SpecimenTemplate SelectSpecimenTemplate(string templateName, int version);
[OperationContract(IsOneWay = true)]
void SpecimenStoredInContainer(string containerID, bool isValidRackID, int rackRow, int rackCol, int deckRow, int deckCol,
int drawerRow, int drawerCol, long trackingNumber, RobotErrors robotError);
[OperationContract(IsOneWay = true)]
void LvssRobotStatusChange(LVSSStatus status);
当我将方法 LvssRobotStatuschange 操作合同设置为 IsOneWay = true 时,我的缓存回调通道会引发 CommunicationObjectAbortedException。由于某种原因,我的回调属性被中止。
***什么会导致回调通道中止?
【问题讨论】:
你的应用托管是什么(服务或回调接口)? 我的应用程序正在托管回调接口。从我的 wpf 视图模型中,我创建了一个代理类,负责创建代理和双工通道。然后我在该类上调用 Connect() 来创建双工通道。代理类负责为我的客户端实现回调接口。在我的场景中,总会有一个服务和一个客户端。 【参考方案1】:我之前遇到过这个问题,this link 应该会有所帮助,它讨论了在应用程序主线程以外的线程上创建通道。
【讨论】:
很好的链接,谢谢!就我而言,我没有在 wpf 视图模型中创建双工通道。通道是在辅助代理类中创建的。当我尝试在该类中创建一个新线程时,通道由于某种原因保持为空。请参阅上面的第一条编辑。 我将您的答案标记为答案,因为它帮助我了解如何启动新线程以防止我的 GUI 锁定。我只为阻塞应用程序的方法启动了一个新线程,而不是整个双工通道。我的 ServiceContract 中的一些 void 方法也缺少一些 IsOneWay 分配,这导致了我的 InvalidOperationExceptions。因此,我使用 InstanceContextMode.Single 和 UseSynchronizationContext = true 的可重入并发模式工作得很好。始终注意您的 ServiceContract 中缺少 IsOneWay 分配!!【参考方案2】:我遇到的问题:
CallBackHandlingMethod()
requestToService(); // deadlock message.
出路:
CallBackHandlingMethod()
Task.Factory.StartNew(()=>
requestToService();
);
【讨论】:
这对我有用,谢谢。我在 WPF 事件中使用 WCF =S【参考方案3】:我有类似的问题,我只需添加即可解决
[CallbackBehavior(ConcurrencyMode=ConcurrencyMode.Multiple)]
到我的回调实现。
【讨论】:
【参考方案4】:[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class ServiceCallbackHandler : IServiceCallback
...
【讨论】:
这段代码有什么作用,它是如何解决问题的? 将这些行添加到实现 IServiceCallback 的类中。今天解决了我的问题!【参考方案5】:当使用UseSynchronizationContext = true
和CallbackBehavior.ConcurrencyMode
的值而不是Multiple
时,在从服务调用中进行回调时会造成死锁。调用堆栈如下所示:
btDoSomething_Click
→ Service.DoSomething();
服务器:DoSomething
→ Callback.ReportUpdate();
客户端:在 IO 回调中,CallbackSynchronizationContext.Send(delegate Callback.ReportUpdate(); )
对CallbackSynchronizationContext.Send
的调用挂起,因为它引用了执行btDoSomething_Click
的线程。有很多方法可以摆脱这个循环:
避免使服务同步(通过应用[OperationContract(IsOneWay = true)]
(这会导致客户端在将请求发送到服务器后立即释放 UI 线程,而不是等待来自 Service.DoSomething
的回复)。
使回调不需要 UI 线程(通过应用 [CallbackBehavior(UseSynchronizationContext = false)]
或 [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
(在任何一种情况下,对回调的调用都将从线程池线程进行。如果您需要编组回到 UI 线程,你仍然可以使用 Synchronizationcontext.Post
)
将您的客户端调用更新为 async
(将您的服务合同从 [OperationContract] void DoSomething();
更改为 [OperationContract] Task DoSomethingAsync();
)
TL;DR:在非OneWay
操作中结合使用[CallbackBehavior(UseSynchronizationContext = true)]
进行回调将导致客户端同步上下文出现死锁。如果您需要在同步上下文中使用回调,请更改您的操作合同以使用异步:
旧:
[ServiceContract(CallbackContract = typeof(IControllerServiceCallback))]
public interface IControllerService
[OperationContract]
void OpenDrawer();
新:
[ServiceContract(CallbackContract = typeof(IControllerServiceCallback))]
public interface IControllerService
[OperationContract]
Task OpenDrawerAsync();
【讨论】:
以上是关于防止 WCF 双工回调服务出现死锁问题的主要内容,如果未能解决你的问题,请参考以下文章