• Microsoft .NET
  • WPF и Silverlight
  • Синхронные вызовы веб-службы в Silverlight: Развенчание мифа об исключительной асинхронности

Синхронные вызовы веб-службы в Silverlight: Развенчание мифа об исключительной асинхронности - Эффективное управление каналами

ОГЛАВЛЕНИЕ

Эффективное управление каналами

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

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

Кеширование канала производится в ChannelManager следующим образом:

readonly Dictionary<Type, object> channels = new Dictionary<Type, object>();
readonly object channelsLock = new object();

public TChannel GetChannel<TChannel>()
{
    Type serviceType = typeof(TChannel);
    object service;

    lock (channelsLock)
    {
        if (!channels.TryGetValue(serviceType, out service))
        {
            /* Фабрика не кешируется, так как она содержит список каналов,
             * которые не удаляются при наступлении ошибки. */
            var channelFactory = new ChannelFactory<TChannel>("*");

            service = channelFactory.CreateChannel();
            var communicationObject = (ICommunicationObject)service;
            communicationObject.Faulted += OnChannelFaulted;
            channels.Add(serviceType, service);
            communicationObject.Open(); /* Явное открытие канала
                         * предотвращает снижение производительности.  */
            ConnectIfClientService(service, serviceType);
        }
    }

    return (TChannel)service;
}

Если в канале происходит ошибка, он удаляется из кеша, как показано в следующем фрагменте:

/// <summary>
/// Вызывается при ошибке в канале.
/// Удаляет канал из кеша, чтобы он
/// заменился, когда потребуется в следующий раз.
/// </summary>
/// <param name="sender">Отправитель.</param>
/// <param name="e"> <see cref="System.EventArgs"/>
/// экземпляр, содержащий данные о событии</param>
void OnChannelFaulted(object sender, EventArgs e)
{
    var communicationObject = (ICommunicationObject)sender;
    communicationObject.Faulted -= OnChannelFaulted;

    lock (channelsLock)
    {
        var keys = from pair in channels
                   where pair.Value == communicationObject
                   select pair.Key;

        /* Удаляются все элементы, соответствующие каналу.
         * Это для защиты, так как должен быть только один экземпляр
         * канала в словаре каналов. */
        foreach (var key in keys.ToList())
        {
            channels.Remove(key);
        }
    }
}

Функциональное программирование с сигнатурами методов известного контракта службы

Если канал службы содержит сигнатуры методов BeginInitiateConnection и EndInitiateConnection, они будут вызваны автоматически при первом создании канала. Инициация соединения при создании канала позволяет рано прервать работу.
При условии, что в основном используются генерируемые посредники, был предоставлен способ вызова метода для запуска канала службы при создании канала. Чтобы сделать это, было решено использовать отражение для поиска метода по имени InitiateConnection. Конечно, генерируемый посредник будет иметь BeginInitiateConnection и EndInitiateConnection, и при создании канала вызываются эти методы, как показано в следующем фрагменте.

/// Пытается выполнить метод <code>IServiceContract.InitiateConnection</code>
/// на заданной службе, если эта служба - <code>IServiceContract</code>.
/// То есть, если служба имеет метод с сигнатурой InitiateConnection(string),
/// он будет вызван функционально.
/// </summary>
/// <param name="service">Служба для попытки осуществления соединения.</param>
/// <param name="serviceType">Тип службы для целей журналирования.</param>
/// <exception cref="TargetInvocationException">Возникает, если служба реализует <code>IServiceContract</code>,
/// и вызов ConnectFromClient приводит к <code>TargetInvocationException</code></exception>
void ConnectIfClientService(object service, Type serviceType)
{
    var beginMethodInfo = serviceType.GetMethod("BeginInitiateConnection");
    if (beginMethodInfo == null)
    {
        return;
    }

    beginMethodInfo.Invoke(service, new object[] { ChannelIdentifier, new AsyncCallback(ar =>
           {
            var endMethodInfo = serviceType.GetMethod("EndInitiateConnection");
            if (endMethodInfo == null)
            {
                return;
            }
            try
            {
                var result = (string)endMethodInfo.Invoke(service, new object[] {ar});
                Debug.WriteLine("Connected from client successfully. Result: " + result);
                /* Делать что-то с результатом, например, записать его где-то. */
            }
            catch (InvalidCastException)
            {
                /* Записать, что веб-сервер имеет неверную сигнатуру ConnectFromClient. */
            }
           }), null });
}