Служба Windows WCF - длинные операции / обратный вызов вызывающего модуля - PullRequest
10 голосов
/ 08 марта 2010

У меня есть служба Windows, которая берет имя группы файлов и выполняет с ними операции (zip / unzip, обновление базы данных и т. Д.). Операции могут занимать время в зависимости от размера и количества файлов, отправляемых в службу.

(1) Модуль, отправляющий запрос в эту службу, ожидает обработки файлов. Я хочу знать, есть ли способ обеспечить обратный вызов в службе, который уведомит вызывающий модуль, когда он закончит обработку файлов. Обратите внимание, что несколько модулей могут вызывать сервис одновременно для обработки файлов, поэтому сервису потребуется предоставить какой-то TaskId, который я предполагаю.

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

Ответы [ 3 ]

14 голосов
/ 08 марта 2010

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

Однако, на мой взгляд, этот механизм довольно ненадежен и не рекомендуется.

В таком случае, когда вызов вызывает довольно длительную операцию, я бы сделал что-то вроде этого:

Если вы хотите придерживаться привязок HTTP / NetTcp, я бы:

  • сбросьте запрос со службой, а затем «отпустите» - это будет односторонний вызов, вы просто отбросите то, что хотите сделать, и тогда ваш клиент завершит работу
  • имеет статусный вызов, по которому клиент может позвонить через определенное время, чтобы выяснить, готовы ли к настоящему моменту результаты запроса
  • если они есть, должен быть третий сервисный вызов для получения результатов

Так что в вашем случае вы можете оставить запрос на сжатие некоторых файлов. Служба отключится, выполнит свою работу и сохранит полученный ZIP во временном местоположении. Затем клиент может проверить, готов ли ZIP, и если да, извлечь его.

Это работает даже лучше в очереди сообщений (MSMQ), которая присутствует на каждом компьютере с Windows-сервером (но, похоже, не многие знают об этом или используют его):

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

Узнайте, как сделать все это эффективно, прочитав отличную статью MSDN. Обращения: Создайте службу ответов WCF для очереди - настоятельно рекомендуется!

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

1 голос
/ 22 мая 2017

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

В чем проблема при длительной эксплуатации? Основная проблема заключается в блокировке интерфейса клиента во время выполнения операции сервером, и с помощью дуплексной службы WCF мы можем использовать обратный вызов клиенту, чтобы избежать блокировки (это старый метод, позволяющий избежать блокировки, но его можно легко преобразовать в инфраструктура async / await с использованием TaskCompletionSource).

Короче говоря, решение использует метод для асинхронного запуска операции на сервере и немедленного возврата. Когда результаты готовы, сервер возвращает их с помощью обратного вызова клиента.

Во-первых, вы должны следовать любому стандартному руководству по созданию дуплексных служб и клиентов WCF, и я нашел эти два полезных:

MSDN дуплексный сервис

Codeproject Статья WCF Duplex Service

Затем выполните следующие шаги, добавив свой собственный код:

  1. Определите интерфейс обратного вызова с помощью метода менеджера событий для отправки результатов с сервера и получения их в клиенте.

    public interface ILongOperationCallBack
    { 
        [OperationContract(IsOneWay = true)]
        void OnResultsSend(....);        
    }
    
  2. Определить интерфейс службы с помощью метода для передачи параметров, необходимых для длительной операции (см. Предыдущий интерфейс ILongOperationCallBack в CallBackContractAttribute)

    [ServiceContract(CallbackContract=typeof(ILongOperationCallBack))]
    public interface ILongOperationService
    {
        [OperationContract]
        bool StartLongOperation(...);
    }
    
  3. В классе Service, который реализует интерфейс службы, сначала получают прокси клиентского обратного вызова и сохраняют его в поле класса , затем запускают длинную операцию асинхронно и возвращают bool цени сразу. После завершения длительной работы отправьте результаты клиенту, используя поле прокси обратного вызова клиента.

    public class LongOperationService:ILongOperationService
    {
        ILongOperationCallBack clientCallBackProxy;
        public ILongOperationCallBack ClientCallBackProxy
        {
            get
            {
                return OperationContext.Current.GetCallbackChannel<ITrialServiceCallBack>());
            }
        }
    
        public bool StartLongOperation(....)
        {
            if(!server.IsBusy)
            {
                 //set server busy state
                //**Important get the client call back proxy here and save it in a class field.**
                this.clientCallBackProxy=ClientCallBackProxy;
                //start long operation in any asynchronous way
                ......LongOperationWorkAsync(....)
                return true; //return inmediately
            }
            else return false;
        }
    
        private void LongOperationWorkAsync(.....)
        {
            .... do work...
            //send results when finished using the cached client call back proxy
            this.clientCallBackProxy.SendResults(....);
            //clear server busy state
        }
        ....
    }
    
  4. В клиенте создайте класс, который реализует ILongOperationCallBack для получения результатов, и добавьте метод для запуска длинной операции на сервере (метод start и менеджер событий не обязательно должны находиться в одном классе)

    public class LongOperationManager: ILongOperationCallBack
    {
    
        public busy StartLongOperation(ILongOperationService server, ....)
        {
            //here you can make the method async using a TaskCompletionSource
            if(server.StartLongOperation(...)) Console.WriteLine("long oper started");
            else Console.Writeline("Long Operation Server is busy")
        }
    
        public void OnResultsSend(.....)
        {
            ... use long operation results..
            //Complete the TaskCompletionSource if you used one
        }
    }
    

ПРИМЕЧАНИЯ:

  1. Я использую возврат bool в методе StartLongOperation, чтобы указать, что сервер занят, а не выключен, но это необходимо, только когда длинная операция не может быть параллельной, как в моем реальном приложении, и, возможно, там являются лучшими способами в WCF для достижения не параллелизма (чтобы определить, не работает ли сервер, добавьте блок Try / Catch как обычно).

  2. Важная цитата, которую я не видел документированной, - это необходимость кэшировать прокси-клиент обратного вызова в методе StartLongOperation. Моя проблема заключалась в том, что я пытался получить прокси в рабочем методе (да, во всех примерах используется клиентский прокси с обратным вызовом в сервисном методе, но в документации описана не простота, а в случае Для длительной работы мы должны отложить обратный вызов до завершения операции).

  3. Не получать и не кэшировать дважды прокси-сервер обратного вызова после возврата метода службы и до следующего.

Отказ от ответственности: я не добавил код для контроля ошибок и т. Д.

1 голос
/ 08 марта 2010

(1) Самый простой способ добиться этого - указать идентификатор задачи, как вы заметили, а затем использовать другой метод IsTaskComplete, с помощью которого клиент может проверить, была ли задача выполнена.

(2) Дополнительные вызовы, сделанные в службу, начнут новые темы.

edit: стандартное поведение службы - запуск новых потоков для каждого вызова. Настраиваемое свойство: Режим контекста экземпляра , для него можно задать PerCall, PerSession или Shareable.

...