Служба WCF с обратными вызовами из фонового потока? - PullRequest
8 голосов
/ 09 апреля 2010

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

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

Прямо сейчас, когда я вызываю OperationContext.Current.GetCallbackChannel<IMyCallback>(), OperationContext имеет значение null. Есть ли стандартная схема, чтобы обойти это?

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

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

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

    [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 AgentEvent для заполнения данных в пользовательском интерфейсе. Как передать экземпляр OperationContext.Current из основного потока в фоновый поток?

Ответы [ 3 ]

5 голосов
/ 09 апреля 2010

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 и передать callback в качестве параметра.


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

Похоже, что здесь происходит то, что 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(...);
    }
}

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

0 голосов
/ 16 июня 2012

Вы не представили IVendorServer, но на тот случай, если вы не знаете, для этого требуется следующий атрибут:

[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IMyCallback))]

Кроме того, вам не нужен фоновый поток для получения событий (я даже не уверен, если и как это применимо). Все, что вам нужно, это реализовать API обратного вызова в классе, который расширяет IMyCallback. В этом API находится ответ.

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

0 голосов
/ 09 апреля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...