Поделиться через


Инициализация создания экземпляров

Пример инициализации расширяет образец пула путем определения интерфейса, IObjectControlкоторый настраивает инициализацию объекта путем активации и деактивации. Клиент вызывает методы, которые возвращают объект в пул, и методы, которые не возвращают объект в пул.

Примечание.

Процедура настройки и инструкции по построению для данного образца приведены в конце этого раздела.

Точки расширяемости

Первым шагом в создании расширения Windows Communication Foundation (WCF) является решение точки расширяемости, используемой. В WCF термин EndpointDispatcher относится к компоненту среды выполнения, ответственному за преобразование входящих сообщений в вызовы методов в службе пользователя и преобразование возвращаемых значений из этого метода в исходящее сообщение. Служба WCF создает EndpointDispatcher для каждой конечной точки.

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

IInstanceProvider

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

  • GetInstance. Когда прибывает сообщение, объект Dispatcher вызывает метод GetInstance, чтобы создать экземпляр класса службы для обработки сообщения. Частота вызовов этого метода определяется свойством InstanceContextMode. Например, если свойство InstanceContextMode имеет значение InstanceContextMode.PerCall, для обработки каждого получаемого сообщения создается новый экземпляр класса службы, поэтому метод GetInstance вызывается каждый раз, когда приходит сообщение.

  • ReleaseInstance. Когда экземпляр службы завершает обработку сообщения, EndpointDispatcher вызывает метод ReleaseInstance. Как и в случае метода GetInstance, частота вызовов этого метода определяется свойством InstanceContextMode.

Пул объектов

Класс ObjectPoolInstanceProvider содержит реализацию пула объектов. Этот класс реализует интерфейс IInstanceProvider для взаимодействия с уровнем модели службы. Когда EndpointDispatcher вызывает метод GetInstance, вместо создания нового экземпляра, пользовательская реализация находит существующий объект в находящемся в памяти пуле. Если такой объект доступен, метод возвращает его. В противном случае ObjectPoolInstanceProvider проверяет, не достигло ли свойство ActiveObjectsCount (количество возвращенных из пула объектов) максимального размера пула. Если нет, то создается новый экземпляр, который возвращается вызывающей стороне, а затем значение ActiveObjectsCount увеличивается на единицу. В противном случае запрос на создание объекта помещается в очередь на заданное время. Реализация метода GetObjectFromThePool показана в следующем образце кода.

private object GetObjectFromThePool()
{
    bool didNotTimeout =
       availableCount.WaitOne(creationTimeout, true);
    if(didNotTimeout)
    {
         object obj = null;
         lock (poolLock)
        {
             if (pool.Count != 0)
             {
                   obj = pool.Pop();
                   activeObjectsCount++;
             }
             else if (pool.Count == 0)
             {
                   if (activeObjectsCount < maxPoolSize)
                   {
                        obj = CreateNewPoolObject();
                        activeObjectsCount++;

                        #if (DEBUG)
                        WritePoolMessage(
                             ResourceHelper.GetString("MsgNewObject"));
                       #endif
                   }
            }
           idleTimer.Stop();
      }
     // Call the Activate method if possible.
    if (obj is IObjectControl)
   {
         ((IObjectControl)obj).Activate();
   }
   return obj;
}
throw new TimeoutException(
ResourceHelper.GetString("ExObjectCreationTimeout"));
}

Пользовательская реализация ReleaseInstance добавляет освободившийся экземпляр обратно в пул и уменьшает значение ActiveObjectsCount на единицу. EndpointDispatcher может вызывать эти методы из различных потоков, поэтому требуется синхронизированный доступ к членам уровня класса в классе ObjectPoolInstanceProvider.

public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
    lock (poolLock)
    {
        // Check whether the object can be pooled.
        // Call the Deactivate method if possible.
        if (instance is IObjectControl)
        {
            IObjectControl objectControl = (IObjectControl)instance;
            objectControl.Deactivate();

            if (objectControl.CanBePooled)
            {
                pool.Push(instance);

                #if(DEBUG)
                WritePoolMessage(
                    ResourceHelper.GetString("MsgObjectPooled"));
                #endif
            }
            else
            {
                #if(DEBUG)
                WritePoolMessage(
                    ResourceHelper.GetString("MsgObjectWasNotPooled"));
                #endif
            }
        }
        else
        {
            pool.Push(instance);

            #if(DEBUG)
            WritePoolMessage(
                ResourceHelper.GetString("MsgObjectPooled"));
            #endif
        }

        activeObjectsCount--;

        if (activeObjectsCount == 0)
        {
            idleTimer.Start();
        }
    }

    availableCount.Release(1);
}

Этот ReleaseInstance метод предоставляет функцию очистки инициализации. Обычно пул поддерживает минимальное число объектов в течение времени существования пула. Однако возможны периоды интенсивного использования, когда требуется создавать в пуле дополнительные объекты, пока не будет достигнуто заданное в конфигурации максимальное значение. В конце концов, когда активность пула снизится, эти дополнительные объекты могут стать излишней нагрузкой. Поэтому когда значение activeObjectsCount достигает нуля, запускается таймер бездействия, который запускает и выполняет цикл очистки.

if (activeObjectsCount == 0)
{
    idleTimer.Start();
}

Расширения уровня ServiceModel подключаются с помощью следующих поведений.

  • Поведения служб. Позволяют настраивать всю среду выполнения службы.

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

  • Поведения контрактов: Эти позволяют настраивать классы ClientRuntime или DispatchRuntime, соответственно, на стороне клиента или на стороне службы.

  • Поведение операций: Позволяют кастомизировать классы ClientOperation или DispatchOperation на стороне клиента или службы соответственно.

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

  • с помощью пользовательского атрибута;

  • Добавляя его императивным способом в коллекцию поведений описания службы.

  • путем расширения файла конфигурации.

В этом образце используется пользовательский атрибут. ServiceHost При построении конструктора он проверяет атрибуты, используемые в определении типа службы, и добавляет доступные поведения в коллекцию поведений в описании службы.

Интерфейс IServiceBehavior имеет три метода: Validate,AddBindingParameters, и .ApplyDispatchBehavior Эти методы вызываются WCF во время инициализации ServiceHost. Метод IServiceBehavior.Validate вызывается первым; это позволяет проверить службу на наличие несогласованностей. Затем вызывается метод IServiceBehavior.AddBindingParameters, который используется только в очень сложных сценариях. Метод IServiceBehavior.ApplyDispatchBehavior вызывается в последнюю очередь и отвечает за настройку среды выполнения. Следующие параметры передаются в IServiceBehavior.ApplyDispatchBehavior.

  • Description: этот параметр содержит описание службы для всей службы. Это можно использовать для проверки данных описания конечных точек службы, контрактов, привязок и других данных, связанных с службой.

  • ServiceHostBase: этот параметр содержит инициализируемый в данный момент объект ServiceHostBase.

В пользовательской реализации IServiceBehavior создается новый экземпляр ObjectPoolInstanceProvider, который присваивается свойству InstanceProvider в каждом объекте EndpointDispatcher, прикрепленном к ServiceHostBase.

public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
    if (enabled)
    {
        // Create an instance of the ObjectPoolInstanceProvider.
        instanceProvider = new ObjectPoolInstanceProvider(description.ServiceType,
        maxPoolSize, minPoolSize, creationTimeout);

        // Assign our instance provider to Dispatch behavior in each
        // endpoint.
        foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
        {
             ChannelDispatcher cd = cdb as ChannelDispatcher;
             if (cd != null)
             {
                 foreach (EndpointDispatcher ed in cd.Endpoints)
                 {
                        ed.DispatchRuntime.InstanceProvider = instanceProvider;
                 }
             }
         }
     }
}

Помимо реализации интерфейса IServiceBehavior у класса ObjectPoolingAttribute имеется несколько членов для настройки пула объектов с помощью аргументов атрибута. К этим членам относятся MaxSize, MinSize, Enabled и CreationTimeout, чтобы соответствовать набору возможностей пула объектов, предоставляемому .NET Enterprise Services.

Теперь поведение пула объектов можно добавить в службу WCF, добавив атрибут в реализацию службы с новым пользовательским ObjectPooling атрибутом.

[ObjectPooling(MaxSize=1024, MinSize=10, CreationTimeout=30000]
public class PoolService : IPoolService
{
  // …
}

Активация и деактивация связывания

Основной целью создания пулов объектов является оптимизация использования объектов с небольшим временем существования, чтобы экономить на ресурсоемких процедурах создания и инициализации. Поэтому, при правильном использовании, это может значительно повысить производительность приложения. Поскольку объект возвращается из пула, конструктор вызывается только один раз. Однако некоторые приложениям требуется некоторый уровень контроля, чтобы они могли инициализировать и очищать ресурсы одного контекста. Например, объект, используемый для набора вычислений, может обнулять значения своих закрытых полей, прежде чем приступать к следующему вычислению. Службы Enterprise Services поддерживают такую инициализацию в зависимости от контекста, позволяя разработчику объектов переопределять методы Activate и Deactivate из базового класса ServicedComponent.

Пул объектов вызывает метод Activate непосредственно перед возвращением объекта из пула. Метод Deactivate вызывается при возвращении объекта в пул. Кроме того, у базового класса ServicedComponent имеется свойство типа boolean с именем CanBePooled, с помощью которого можно уведомить пул о том, можно ли и дальше размещать объект в пуле.

Чтобы сымитировать эту функциональность, в этом образце объявляется открытый интерфейс (IObjectControl), имеющий указанные выше члены. Затем этот интерфейс реализуется классами службы, предназначенными для инициализации с учетом контекста. Реализацию IInstanceProvider необходимо изменить, чтобы она удовлетворяла этим требованиям. Теперь каждый раз, когда вы получите объект, вызывая GetInstance метод, необходимо проверить, реализует IObjectControl. ли объект, если это делает, необходимо вызвать Activate метод соответствующим образом.

if (obj is IObjectControl)
{
    ((IObjectControl)obj).Activate();
}

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

if (instance is IObjectControl)
{
    IObjectControl objectControl = (IObjectControl)instance;
    objectControl.Deactivate();
    if (objectControl.CanBePooled)
    {
       pool.Push(instance);
    }
}

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

// Remove the surplus objects.
if (pool.Count > minPoolSize)
{
  // Clean the surplus objects.
}
else if (pool.Count < minPoolSize)
{
  // Reinitialize the missing objects.
  while(pool.Count != minPoolSize)
  {
    pool.Push(CreateNewPoolObject());
  }
}

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

Установка, сборка и выполнение примера

  1. Убедитесь, что вы выполнили процедуру однократной настройки для образцов Windows Communication Foundation.

  2. Чтобы создать решение, следуйте инструкциям в разделе Building the Windows Communication Foundation Samples.

  3. Чтобы запустить пример в конфигурации с одним или несколькими компьютерами, следуйте инструкциям в разделе "Примеры Windows Communication Foundation".