13

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

此操作将死锁,因为在当前消息完成处理之前无法接收到回复。如果要允许无序消息处理,请在 CallbackBehaviorAttribute 上指定 Reentrant 或 Multiple 的 ConcurrencyMode。

这是我的服务行为:

[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()快速调用时冻结我的客户端 wpf 应用程序(通过快速单击 UI 按钮)。该方法不是一种方式,而是从服务同步返回一个枚举类型给客户端。

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

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

4

5 回答 5

14

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

于 2013-01-18T09:51:22.817 回答
6

我遇到的问题:

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

出路:

CallBackHandlingMethod()
{
    Task.Factory.StartNew(()=>
    {
        requestToService();
    });
}
于 2014-03-20T08:52:47.967 回答
2

我有类似的问题,我只需添加即可解决

[CallbackBehavior(ConcurrencyMode=ConcurrencyMode.Multiple)]

到我的回调实现。

于 2017-01-16T20:32:06.643 回答
0
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class ServiceCallbackHandler : IServiceCallback
{
 ...
}
于 2017-01-23T12:46:52.277 回答
0

当使用除 之外的任何UseSynchronizationContext = true值时,在从服务调用中进行回调时,您正在创建死锁。调用堆栈如下所示:CallbackBehavior.ConcurrencyModeMultiple

  • 客户: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();


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

老的:

[ServiceContract(CallbackContract = typeof(IControllerServiceCallback))]
public interface IControllerService
{

    [OperationContract]
    void OpenDrawer();
}

新的:

[ServiceContract(CallbackContract = typeof(IControllerServiceCallback))]
public interface IControllerService
{
    [OperationContract]
    Task OpenDrawerAsync();
}
于 2018-06-13T23:43:18.870 回答