Instancing Initialization

The Initialization sample extends the Pooling sample by defining an interface, IObjectControl, which customizes the initialization of an object by activating and deactivating it. The client invokes methods that return the object to the pool and that do not return the object to the pool.

Note

The setup procedure and build instructions for this sample are located at the end of this topic.

Extensibility Points

The first step in creating a Windows Communication Foundation (WCF) extension is to decide the extensibility point to use. In WCF, the term EndpointDispatcher refers to a run-time component responsible for converting incoming messages into method invocations on the user's service and for converting return values from that method to an outgoing message. A WCF service creates an EndpointDispatcher for each endpoint.

The EndpointDispatcher offers endpoint scope (for all messages received or sent by the service) extensibility using the EndpointDispatcher class. This class allows you to customize various properties that control the behavior of the EndpointDispatcher. This sample focuses on the InstanceProvider property that points to the object that provides the instances of the service class.

IInstanceProvider

In WCF, the EndpointDispatcher creates instances of a service class by using an instance provider that implements the IInstanceProvider interface. This interface has only two methods:

The Object Pool

The ObjectPoolInstanceProvider class contains the implementation for the object pool. This class implements the IInstanceProvider interface to interact with the service model layer. When the EndpointDispatcher calls the GetInstance method, instead of creating a new instance, the custom implementation looks for an existing object in an in-memory pool. If one is available, it is returned. Otherwise, ObjectPoolInstanceProvider checks whether the ActiveObjectsCount property (number of objects returned from the pool) has reached the maximum pool size. If not, a new instance is created and returned to the caller and ActiveObjectsCount is subsequently incremented. Otherwise an object creation request is queued for a configured period of time. The implementation for GetObjectFromThePool is shown in the following sample code.

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"));
}

The custom ReleaseInstance implementation adds the released instance back to the pool and decrements the ActiveObjectsCount value. The EndpointDispatcher can call these methods from different threads, and therefore synchronized access to the class level members in the ObjectPoolInstanceProvider class is required.

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);
}

The ReleaseInstance method provides a clean up initialization feature. Normally the pool maintains a minimum number of objects for the lifetime of the pool. However, there can be periods of excessive usage that require creating additional objects in the pool to reach the maximum limit specified in the configuration. Eventually when the pool becomes less active those surplus objects can become an extra overhead. Therefore when the activeObjectsCount reaches zero an idle timer is started that triggers and performs a clean-up cycle.

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

ServiceModel layer extensions are hooked up using the following behaviors:

  • Service Behaviors: These allow for the customization of the entire service runtime.

  • Endpoint Behaviors: These allow for the customization of a particular service endpoint, including the EndpointDispatcher.

  • Contract Behaviors: These allow for the customization of either ClientRuntime or DispatchRuntime classes on the client or the service respectively.

  • Operation Behaviors: These allow for the customization of either ClientOperation or DispatchOperation classes on the client or the service respectively.

For the purpose of an object pooling extension, either an endpoint behavior or a service behavior can be created. In this example, we use a service behavior, which applies object pooling ability to every endpoint of the service. Service behaviors are created by implementing the IServiceBehavior interface. There are several ways to make the ServiceModel aware of the custom behaviors:

  • Using a custom attribute.

  • Imperatively adding it to the service description's behaviors collection.

  • Extending the configuration file.

This sample uses a custom attribute. When the ServiceHost is constructed, it examines the attributes used in the service's type definition and adds the available behaviors to the service description's behaviors collection.

The IServiceBehavior interface has three methods: Validate, AddBindingParameters, and ApplyDispatchBehavior. These methods are called by WCF when the ServiceHost is being initialized. IServiceBehavior.Validate is called first; it allows the service to be inspected for inconsistencies. IServiceBehavior.AddBindingParameters is called next; this method is only required in very advanced scenarios. IServiceBehavior.ApplyDispatchBehavior is called last and is responsible for configuring the runtime. The following parameters are passed into IServiceBehavior.ApplyDispatchBehavior:

  • Description: This parameter provides the service description for the entire service. This can be used to inspect description data about the service's endpoints, contracts, bindings, and other data associated with the service.

  • ServiceHostBase: This parameter provides the ServiceHostBase that is currently being initialized.

In the custom IServiceBehavior implementation, a new instance of ObjectPoolInstanceProvider is instantiated and assigned to the InstanceProvider property in each EndpointDispatcher that is attached to the 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;
                 }
             }
         }
     }
}

In addition to an IServiceBehavior implementation the ObjectPoolingAttribute class has several members to customize the object pool using the attribute arguments. These members include MaxSize, MinSize, Enabled and CreationTimeout, to match the object pooling feature set provided by .NET Enterprise Services.

The object pooling behavior can now be added to a WCF service by annotating the service implementation with the newly created custom ObjectPooling attribute.

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

Hooking Activation and Deactivation

The primary objective of object pooling is to optimize short-lived objects with relatively expensive creation and initialization. Therefore it can give a dramatic performance boost to an application if properly used. Because the object is returned from the pool, the constructor is called only once. However, some applications require some level of control so that they can initialize and clean-up the resources used during a single context. For example, an object being used for a set of calculations can reset its private fields before processing the next calculation. Enterprise Services enabled this kind of context-specific initialization by letting the object developer override Activate and Deactivate methods from the ServicedComponent base class.

The object pool calls the Activate method just before returning the object from the pool. Deactivate is called when the object returns back to the pool. The ServicedComponent base class also has a boolean property called CanBePooled, which can be used to notify the pool whether the object can be pooled further.

To mimic this functionality, the sample declares a public interface (IObjectControl) that has the aforementioned members. This interface is then implemented by service classes intended to provide context specific initialization. The IInstanceProvider implementation must be modified to meet these requirements. Now, each time you get an object by calling the GetInstance method, you must check whether the object implements IObjectControl. If it does, you must call the Activate method appropriately.

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

When returning an object to the pool, a check is required for the CanBePooled property before adding the object back to the pool.

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

Because the service developer can decide whether an object can be pooled, the object count in the pool at a given time can go below the minimum size. Therefore you must check whether the object count has gone below the minimum level and perform the necessary initialization in the clean-up procedure.

// 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());
  }
}

When you run the sample, the operation requests and responses are displayed in both the service and client console windows. Press Enter in each console window to shut down the service and client.

To set up, build, and run the sample

  1. Ensure that you have performed the One-Time Setup Procedure for the Windows Communication Foundation Samples.

  2. To build the solution, follow the instructions in Building the Windows Communication Foundation Samples.

  3. To run the sample in a single- or cross-machine configuration, follow the instructions in Running the Windows Communication Foundation Samples.