防止WCF双工回调服务出现死锁问题

时间:2013-01-18 06:13:54

标签: .net wcf duplex

我遇到了自托管wcf双工回拨服务的问题。我收到一条InvalidOperationException消息:

  

此操作会死锁,因为无法接收回复   直到当前消息完成处理。如果你想允许   乱序消息处理,指定Reentrant的ConcurrencyMode   或CallbackBehaviorAttribute上的Multiple。

这是我的服务行为:

[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。出于某种原因,我的回调属性正在中止。

***什么会导致回拨频道中止?

5 个答案:

答案 0 :(得分:13)

我之前已经遇到过此问题,this link应该提供帮助,讨论在应用程序主线程以外的线程上创建频道。

答案 1 :(得分:5)

我遇到的问题:

CallBackHandlingMethod()
{
    requestToService();    // deadlock message.    
}
出路:

CallBackHandlingMethod()
{
    Task.Factory.StartNew(()=>
    {
        requestToService();
    });
}

答案 2 :(得分:2)

我有类似的问题,我只是通过添加

解决了这个问题

[CallbackBehavior(ConcurrencyMode=ConcurrencyMode.Multiple)]

到我的回调实现。

答案 3 :(得分:0)

[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class ServiceCallbackHandler : IServiceCallback
{
 ...
}

答案 4 :(得分:0)

UseSynchronizationContext = true使用除CallbackBehavior.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();

  4. TL; DR:在非[CallbackBehavior(UseSynchronizationContext = true)]操作中与OneWay进行回调的组合将导致客户端同步上下文出现死锁。如果您需要在同步上下文中使用回调,请将操作合同更改为使用async:

    旧:

    [ServiceContract(CallbackContract = typeof(IControllerServiceCallback))]
    public interface IControllerService
    {
    
        [OperationContract]
        void OpenDrawer();
    }
    

    新:

    [ServiceContract(CallbackContract = typeof(IControllerServiceCallback))]
    public interface IControllerService
    {
        [OperationContract]
        Task OpenDrawerAsync();
    }