Skip to content

Latest commit

 

History

History
257 lines (201 loc) · 15.4 KB

instancing-initialization.md

File metadata and controls

257 lines (201 loc) · 15.4 KB
description title ms.date ms.assetid
Learn more about: Instancing Initialization
Instancing Initialization
03/30/2017
154d049f-2140-4696-b494-c7e53f6775ef

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 xref:System.ServiceModel.Dispatcher.EndpointDispatcher class. This class allows you to customize various properties that control the behavior of the EndpointDispatcher. This sample focuses on the xref:System.ServiceModel.Dispatcher.DispatchRuntime.InstanceProvider%2A 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 xref:System.ServiceModel.Dispatcher.IInstanceProvider interface. This interface has only two methods:

  • xref:System.ServiceModel.Dispatcher.IInstanceProvider.GetInstance%2A: When a message arrives, the Dispatcher calls the xref:System.ServiceModel.Dispatcher.IInstanceProvider.GetInstance%2A method to create an instance of the service class to process the message. The frequency of the calls to this method is determined by the xref:System.ServiceModel.ServiceBehaviorAttribute.InstanceContextMode%2A property. For example if the xref:System.ServiceModel.ServiceBehaviorAttribute.InstanceContextMode%2A property is set to xref:System.ServiceModel.InstanceContextMode.PerCall?displayProperty=nameWithType, a new instance of service class is created to process each message that arrives, so xref:System.ServiceModel.Dispatcher.IInstanceProvider.GetInstance%2A is called whenever a message arrives.

  • xref:System.ServiceModel.Dispatcher.IInstanceProvider.ReleaseInstance%2A: When the service instance finishes processing the message, the EndpointDispatcher calls the xref:System.ServiceModel.Dispatcher.IInstanceProvider.ReleaseInstance%2A method. As in the xref:System.ServiceModel.Dispatcher.IInstanceProvider.GetInstance%2A method, the frequency of the calls to this method is determined by the xref:System.ServiceModel.ServiceBehaviorAttribute.InstanceContextMode%2A property.

The Object Pool

The ObjectPoolInstanceProvider class contains the implementation for the object pool. This class implements the xref:System.ServiceModel.Dispatcher.IInstanceProvider interface to interact with the service model layer. When the EndpointDispatcher calls the xref:System.ServiceModel.Dispatcher.IInstanceProvider.GetInstance%2A 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 xref:System.ServiceModel.Dispatcher.ClientRuntime or xref:System.ServiceModel.Dispatcher.DispatchRuntime classes on the client or the service respectively.

  • Operation Behaviors: These allow for the customization of either xref:System.ServiceModel.Dispatcher.ClientOperation or xref:System.ServiceModel.Dispatcher.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 xref:System.ServiceModel.Description.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 xref:System.ServiceModel.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 xref:System.ServiceModel.Description.IServiceBehavior interface has three methods: xref:System.ServiceModel.Description.IServiceBehavior.Validate%2A, xref:System.ServiceModel.Description.IServiceBehavior.AddBindingParameters%2A, and xref:System.ServiceModel.Description.IServiceBehavior.ApplyDispatchBehavior%2A. These methods are called by WCF when the xref:System.ServiceModel.ServiceHost is being initialized. xref:System.ServiceModel.Description.IServiceBehavior.Validate%2A?displayProperty=nameWithType is called first; it allows the service to be inspected for inconsistencies. xref:System.ServiceModel.Description.IServiceBehavior.AddBindingParameters%2A?displayProperty=nameWithType is called next; this method is only required in very advanced scenarios. xref:System.ServiceModel.Description.IServiceBehavior.ApplyDispatchBehavior%2A?displayProperty=nameWithType is called last and is responsible for configuring the runtime. The following parameters are passed into xref:System.ServiceModel.Description.IServiceBehavior.ApplyDispatchBehavior%2A?displayProperty=nameWithType:

  • 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 xref:System.ServiceModel.ServiceHostBase that is currently being initialized.

In the custom xref:System.ServiceModel.Description.IServiceBehavior implementation, a new instance of ObjectPoolInstanceProvider is instantiated and assigned to the xref:System.ServiceModel.Dispatcher.DispatchRuntime.InstanceProvider%2A property in each xref:System.ServiceModel.Dispatcher.EndpointDispatcher that is attached to the xref:System.ServiceModel.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 xref:System.ServiceModel.Description.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 xref:System.EnterpriseServices.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 xref:System.EnterpriseServices.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 xref:System.ServiceModel.Dispatcher.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.