防止 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 = trueCallbackBehavior.ConcurrencyMode 的值而不是Multiple 时,在从服务调用中进行回调时会造成死锁。调用堆栈如下所示:

客户:btDoSomething_ClickService.DoSomething(); 服务器:DoSomethingCallback.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 双工回调服务出现死锁问题的主要内容,如果未能解决你的问题,请参考以下文章

响应式 WCF 客户端的双工回调或客户端线程

保持线程安全,同时防止可能的同步回调死锁

Wcf 回调网络 tcp 双工仅 1 路故障

WCF 双工回调示例失败

通过 WCF 双工通道长期运行的回调合同 - 替代设计模式?

如何防止 WCF 服务进入故障状态?