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


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

Пример инициализации расширяет образец пула путем определения интерфейса, 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 имеет три метода: AddBindingParameters, Validate, и .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. Чтобы создать решение, следуйте инструкциям по созданию примеров Windows Communication Foundation.

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