Предотвращение взаимоблокировки с помощью службы обратного вызова WCF duplex

у меня проблема с автономной службой обратного вызова WCF duplex. Я получаю InvalidOperationException сообщение:

эта операция будет тупик, потому что ответ не может быть получен пока текущее сообщение не завершит обработку. Если вы хотите позволить для обработки сообщений, указать значения 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 на multiple, а UseSynchronizationContext на false.

Я все еще вижу две проблемы с моей обслуживание:

первый: следующая операция Службы замораживает мое клиентское приложение wpf, когда GetLvssStatus() вызывается быстро (нажав кнопку 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.Потокобезопасное поведение нескольких служб?

любая помощь с этими вопросами очень ценится.

* ПЕРВОЕ РЕДАКТИРОВАНИЕ Немного больше информации о создании моего дуплексного канала. Моя модель представления wpf создает прокси-объект что отвечает за обработку создания моего канала. Любые попытки до сих пор установить мой канал в новом потоке на стороне клиента приводят к ObjectDisposedException когда служба пытается использовать объект обратного вызова.

* ВТОРОЕ РЕДАКТИРОВАНИЕ Я считаю, что моя служба должна работать, если я могу получить контракты на операцию с методом void для установки IsOneWay = true. При параллелизме reentrant основной поток канала должен пропускать эти методы независимо от каких-либо запирающий.
Вот мой интерфейс обратного вызова:

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 Operation contract значение IsOneWay = true, мой кэшированный канал обратного вызова создает исключение CommunicationObjectAbortedException. По какой-то причине мое свойство обратного вызова прерывается.

* * * что может привести к прерыванию канала обратного вызова?

5 ответов


Я уже сталкивался с этим раньше,этой ссылке должно помочь, в котором обсуждается создание каналов в потоке, отличном от основного потока приложений.


проблема с которой я столкнулся:

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)]
    (это заставляет клиента освободить поток пользовательского интерфейса, как только запрос отправляется на сервер, а не ждать ответа от Service.DoSomething).

  2. сделайте обратный вызов не требующим потока пользовательского интерфейса (применяя [CallbackBehavior(UseSynchronizationContext = false)] или [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
    (в любом случае, вызов вашего обратного вызова будет сделан из поток ThreadPool. Если вам нужно маршалировать обратно в поток пользовательского интерфейса, вы все равно можете сделать это с помощью Synchronizationcontext.Post)

  3. обновить клиентские звонки async (измените свой сервисный контракт с [OperationContract] void DoSomething(); to [OperationContract] Task DoSomethingAsync();)


TL; DR: комбинация обратного вызова с [CallbackBehavior(UseSynchronizationContext = true)] С номера OneWay операция вызовет взаимоблокировку в контексте синхронизации вашего клиента. Измените контракт на использование async, если вам нужны обратные вызовы, чтобы быть в контексте синхронизации:

старый:

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

    [OperationContract]
    void OpenDrawer();
}

New:

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