Асинхронный доступ к веб-службам в шаблонах проектирования .NET - Централизованное управление службой

ОГЛАВЛЕНИЕ

Централизованное управление службой

С помощью вышеприведенных реализаций можно запускать несколько асинхронных вызовов разных символов акций и получать результаты в одном обратном вызове или в одном обработчике события. Можно получить котировку для символа, обозначаемого AsyncState или членом UserState. Можно сделать это основанной на службе динамически подключаемой библиотекой (DLL) для нескольких вызывающих функций. Это прекрасно работает, пока каждый вызов создает свой собственный экземпляр класса службы, содержащий копию посредника.

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

 

Теперь обращение клиентов к веб-службе считается двухэтапным. Можно создать класс центра обслуживания и экспортировать метод GetStock() для клиентов (как пример акции в контексте). GetStock() инициирует асинхронный вызов (этап 1), чтобы неявно запустить поток. Эта процедура нового потока создает объект посредника для обращения к веб-службе (этап 2).

Следующий Листинг-5 показывает реализацию класса ServiceAsync с использованием асинхронного метода начало/конец.

// Листинг-5. Класс службы с методом начало/конец

// Определяется класс асинхронной службы
public class ServiceAsync
{
    // Определяется закрытый асинхронный метод Делегат на этапе 1
    private delegate GetStockStatus
            AsyncGetStockDelegate(ref string symbol);
   
    // Альтернативное событие, если никакая процедура обратного вызова не задана
    public event OnGetStockResult OnGetStock;

    // Посредник веб-службы
    private Service _wsProxy1;
   
    // Это открытый метод, вызываемый пользователем
    public void GetStock(string symbol, OnGetStockResult callbackProc)
    {
        // Создается закрытый асинхронный делегат.
        AsyncGetStockDelegate dlgt = new AsyncGetStockDelegate(GetStock);
       
        callbackData data = null;
        if (callbackProc !=null)
        {
            data = new callbackData();
            data._callbackProc = callbackProc;
        }

        // Инициируется асинхронный запрос.
        IAsyncResult ar = dlgt.BeginInvoke(ref symbol,
                          new AsyncCallback(AsyncGetStockResult), data);
    }

    // Это закрытая процедура потока
    private GetStockStatus GetStock(ref string symbol)
    {
        // Этап 2: Используется _wsProxy1 для обращения к веб-службе.
        // Возвращает состояние в GetStockStatus

        _wsProxy1 = new Service();
        ... ... ...
        symbol = value.ToString("F");
        return GetStockStatus.OK;
    }

    // Структура данных обратного вызова
    private class callbackData
    {
        public OnGetStockResult _callbackProc;
        // Затем идут другие данные для передачи и возврата
    }

    // Асинхронный обратный вызов, когда запрос завершен на этапе 1
    private void AsyncGetStockResult(IAsyncResult ar)
    {
        AsyncGetStockDelegate dlgt =
                (AsyncGetStockDelegate)((AsyncResult)ar).AsyncDelegate;
               
        string result = string.Empty;
        GetStockStatus status = dlgt.EndInvoke(ref result, ar);

        callbackData data = (callbackData)ar.AsyncState;
        if (data != null)
        {
            OnGetStockResult callbackProc = data._callbackProc;
            callbackProc(result, status);    // Вызывается заданный пользователем делегат
        }
        else
        if (OnGetStock != null)
            OnGetStock(result, status);      // Если нет делегата, возбуждается событие
    }
   
    public void Cancel()
    {
        _wsProxy1.Abort();
    }
}

// Определяется состояние результата
public enum GetStockStatus { OK, Exception, TimeOut, Invalid, Cancelled }

// Определяется делегат для события, чтобы возбудить результат
public delegate void OnGetStockResult(string result,
                             GetStockStatus status);

Первый открытый GetStock() принимает символ и заданный пользователем обратный вызов. Затем он создает объект AsyncGetStockDelegate, dlgt и готовит данные обратного вызова. Как только dlgt инициирует асинхронный запрос, вызывается второй закрытый GetStock() для выполнения задачи с помощью веб-службы.

Для клиента гибкость в вызове GetStock() состоит в том, что он сделан двойным образом. Посмотрите на внутренний обратный вызов AsyncGetStockResult(). Если пользователь задает процедуру обратного вызова, она используется для возврата результата. Если обратный вызов не задан, возбуждается событие OnGetStock(), чтобы известить вызывающую функцию о входящих результатах. Это можно сделать так:

ServiceAsync sa = new ServiceAsync();
sa.GetStock(symbol, new OnGetStockResult(OnGetStock));

Или аннулировать обратный вызов путем присоединения обработчика события:

as.OnGetStock += new OnGetStockResult(OnGetStock);
as.GetStock(symbol, null);

Альтернатива службе-синглтону для управления несколькими вызовами – порождать поток для каждого вызова пользователя. Листинг-6 показывает такую конструкцию.

// Листинг-6. Класс службы, порождающий поток

// Определяется класс потока службы
public class ServiceThread
{
    // Событие для отправки результата
    public event OnGetStockResult OnGetStock;
   
    // Посредник веб-службы
    private Service _wsProxy2;
    private string  _symbol;
    private int     _timeout;
   
    // Это открытый метод, вызываемый пользователем
    public void GetStock(string symbol)
    {
        _symbol = symbol;
        _wsProxy2 = new Service();
        _wsProxy2.GetStockWithTimeoutCompleted +=
            new GetStockWithTimeoutCompletedEventHandler(GetStockCompleted);
           
        Thread thread = new Thread(new ThreadStart(GetStock));
        thread.Start();
    }

    // Это закрытая процедура потока
    private void GetStock()
    {
        _wsProxy2.GetStockWithTimeoutAsync(_symbol, _timeout, _symbol);
    }

    // Обработчик события GetStockWithTimeoutCompletedEventHandler в .NET 2
    private void GetStockCompleted(Object sender,
                          GetStockWithTimeoutCompletedEventArgs gca)
    {
        // Этап 2: Используется _wsProxy для обращения к веб-службе.
        // В зависимости от результатов в gca, возбуждается событие
        ... ... ...
        OnGetStock(_symbol +" " +gca.Result.ToString("F"), GetStockStatus.OK);
    }
       
    public void Cancel()
    {
        _wsProxy2.Abort();
    }
}

// Определяется состояние результата
public enum GetStockStatus { OK, Exception, TimeOut, Invalid, Cancelled }

// Определяется делегат для события для возбуждения результата
public delegate void OnGetStockResult(string result,
                             GetStockStatus status);

Первый открытый GetStock() явно запускает поток для выполнения второго закрытого GetStock(), который инициирует событийно-управляемый асинхронный вызов с помощью посредника .NET 2.0. Выбор организации поточной обработки или обратного вызова зависит от применения вашего приложения, отношения ресурсов и того, сколько одновременных вызовов производится к вашей системе.