【问题标题】:Prevent deadlock issue with WCF duplex callback service防止 WCF 双工回调服务出现死锁问题
【发布时间】: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() 来创建双工通道。代理类负责为我的客户端实现回调接口。在我的场景中,总会有一个服务和一个客户端。

标签: .net wcf duplex


【解决方案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 的线程。有很多方法可以摆脱这个循环:

    1. 避免使服务同步(通过应用[OperationContract(IsOneWay = true)]
      (这会导致客户端在将请求发送到服务器后立即释放 UI 线程,而不是等待来自 Service.DoSomething 的回复)。

    2. 使回调不需要 UI 线程(通过应用 [CallbackBehavior(UseSynchronizationContext = false)][CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
      (在任何一种情况下,对回调的调用都将从线程池线程进行。如果您需要编组回到 UI 线程,你仍然可以使用 Synchronizationcontext.Post)

    3. 将您的客户端调用更新为 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();
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-20
      • 1970-01-01
      相关资源
      最近更新 更多