Сервис WCF с обратными вызовами, прибывающими из фонового потока?

Вот моя ситуация. Я записал сервис WCF, который звонит в одну из кодовых баз нашего поставщика для выполнения операций, таких как Вход в систему, Выход из системы, и т.д. Требование этой операции - то, что у нас есть фоновый поток для получения событий в результате того действия. Например, действие Входа в систему отправляется на основном потоке. Затем несколько событий получены назад от сервиса поставщика в результате входа в систему. Может быть 1, 2, или несколько полученных событий. Фоновый поток, который работает на таймере, получает эти события и запускает событие в wcf сервис, чтобы уведомить, что прибыло новое событие.

Я реализовал сервис WCF в Дуплексном режиме и запланировал использовать обратные вызовы, чтобы уведомить UI, что события прибыли. Вот мой вопрос: Как я отправляю новые события от фонового потока до потока, который выполняет сервис?

Прямо сейчас, когда я звоню OperationContext.Current.GetCallbackChannel<IMyCallback>(), OperationContext является пустым. Существует ли стандартный шаблон для обхождения этого?

Я использую PerSession в качестве своего SessionMode на ServiceContract.

ОБНОВЛЕНИЕ: Я думал, что сделаю свой точный сценарий более четким путем демонстрации, как я получаю события от кода поставщика. Моя библиотека получает каждое событие, определяет то, что событие и исчерпывает событие для того конкретного возникновения.

У меня есть другой проект, который является библиотекой классов специально для соединения с сервисом поставщика. Я отправлю всю реализацию сервиса для предоставления более четкого изображения:

    [ServiceBehavior(
        InstanceContextMode = InstanceContextMode.PerSession
        )]
    public class VendorServer:IVendorServer
    {
private IVendorService _vendorService;  // This is the reference to my class library

        public VendorServer()
        {
_vendorServer = new VendorServer();
_vendorServer.AgentManager.AgentLoggedIn += AgentManager_AgentLoggedIn; // This is the eventhandler for the event which arrives from a background thread

}

        public void Login(string userName, string password, string stationId)
        {
            _vendorService.Login(userName, password, stationId); // This is a direct call from the main thread to the vendor service to log in
        }

    private void AgentManager_AgentLoggedIn(object sender, EventArgs e)
    {

        var agentEvent = new AgentEvent
                             {
                                 AgentEventType = AgentEventType.Login,
                                 EventArgs = e
                             };
    }
}

Объект AgentEvent содержит обратный вызов как одно из его свойств, и я думал, что выполню обратный вызов как это:

agentEvent.Callback = OperationContext.Current.GetCallbackChannel<ICallback>();

AgentEvent является объектом, определенным в сервисе:

[DataContract]
public class AgentEvent
{
    [DataMember]
    public EventArgs EventArgs { get; set; }
    [DataMember]
    public AgentEventType AgentEventType { get; set; }
    [DataMember]
    public DateTime TimeStamp{ get; set; }
    [DataMember]
    public IVendorCallback Callback { get; set; }
}

IVendorCallback похож на это:

    public interface IVendorCallback
    {
        [OperationContract(IsOneWay = true)]
        void SendEvent(AgentEvent agentEvent);
    }

Обратный вызов реализован на клиенте и использует EventArgs porperty AgentEvent для заполнения данных по UI. Как я передал бы OperationContext. Текущий экземпляр от основного потока в фоновый поток?

8
задан Mark Struzinski 9 April 2010 в 14:50
поделиться

2 ответа

OperationContext.Current доступен только в потоке, который фактически выполняет операцию. Если вы хотите, чтобы он был доступен рабочему потоку, вам нужно фактически передать ссылку на канал обратного вызова этому потоку.

Таким образом, ваша операция может выглядеть примерно так:

public class MyService : IMyService
{
    public void Login()
    {
        var callback = 
            OperationContext.Current.GetCallbackChannel<ILoginCallback>();
        ThreadPool.QueueUserWorkItem(s =>
        {
            var status = VendorLibrary.PerformLogin();
            callback.ReportLoginStatus(status);
        });
    }
}

Это простой способ сделать это, используя ThreadPool и захват переменных анонимного метода. Если вы хотите сделать это с помощью свободно работающего потока, вам придется использовать вместо него ParameterizedThreadStart и передать обратный вызов в качестве параметра.


Обновление для конкретного примера:

Похоже, здесь происходит то, что IVendorService использует некоторую управляемую событиями модель для обратных вызовов.

Поскольку вы используете InstanceContextMode.PerSession , вы можете просто сохранить обратный вызов в частном поле самого класса службы, а затем ссылаться на это поле в обработчике событий.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class VendorServer : IVendorServer
{
    private IMyCallback callback;
    private IVendorService vendorService;

    public VendorServer()
    {
        callback = OperationContext.Current.GetCallbackChannel<IMyCallback>();
        vendorService = new VendorService();
        vendorService.AgentManager.AgentLoggedIn += AgentManager_AgentLoggedIn;
    }

    public void Login(string userName, string password, string stationId)
    {
        vendorService.Login(userName, password, stationId);
    }

    private void AgentManager_AgentLoggedIn(object sender, EventArgs e)
    {
        callback.ReportLoggedIn(...);
    }
}

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

5
ответ дан 5 December 2019 в 23:14
поделиться

Поместите рассматриваемые события в потокобезопасную (заблокированную) очередь и пусть выполняющая служба (как вы ее называете) проверяет счетчик очереди. Удалите из очереди по мере необходимости.

0
ответ дан 5 December 2019 в 23:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: