繁体   English   中英

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

[英]Prevent deadlock issue with WCF duplex callback service

我有一个自托管的wcf双工回调服务的问题。 我收到带有消息的InvalidOperationException

此操作将死锁,因为在当前消息完成处理之前无法接收回复。 如果要允许无序消息处理,请在CallbackBehaviorAttribute上指定Remediate的ConcurrencyMode或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服务行为线程安全的最佳做法是什么?

对这些问题的任何帮助都非常感谢。

* FIRST EDIT关于创建双工通道的更多信息。 我的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。 出于某种原因,我的回调属性正在中止。

***什么可能导致回调通道中止?

我之前遇到过这个问题, 这个链接应该有帮助,它讨论了在应用程序主线程以外的线程上创建通道。

我遇到的问题:

CallBackHandlingMethod()
{
    requestToService();    // deadlock message.    
}

出路:

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

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

[CallbackBehavior(ConcurrencyMode=ConcurrencyMode.Multiple)]

到我的回调实现。

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

当使用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] 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();
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM