Partager via


Pooling

Cet exemple montre comment étendre Windows Communication Foundation (WCF) afin de prendre en charge le pool d'objet. L'exemple montre comment créer un attribut syntaxiquement et sémantiquement similaire aux fonctionnalités de l'attribut ObjectPoolingAttribute de Enterprise Services. Le pool d'objets permet une amélioration significative de la performance d'une application. Toutefois, il peut avoir l'effet inverse s'il n'est pas utilisé de manière appropriée. Le pool d'objets évite d'avoir à recréer les objets fréquemment utilisés qui requièrent une initialisation complète. Toutefois, si un appel à une méthode sur un objet du pool met beaucoup de temps à s'exécuter, le pool d'objets met les demandes supplémentaires en file d'attente dès que la taille de pool maximale est atteinte. Il peut donc ne pas traiter certaines demandes de création d'objet en levant une exception de délai d'attente.

ms751482.note(fr-fr,VS.90).gifRemarque :
La procédure d'installation ainsi que les instructions de génération relatives à cet exemple figurent en fin de rubrique.

La première étape de création d'une extension WCF consiste à déterminer le point d'extensibilité à utiliser.

Dans WCF, le répartiteur fait référence à un composant runtime chargé de convertir des messages entrants en appels de méthode sur le service de l'utilisateur et de convertir les valeurs de retour de cette méthode en message sortant. Un service WCF crée un répartiteur pour chaque point de terminaison. Un client WCF doit utiliser un répartiteur si le contrat qui lui est associé est duplex.

Les répartiteurs de canal et de point de terminaison offrent une extensibilité au niveau du contrat et du canal en exposant diverses propriétés qui contrôlent le comportement du répartiteur. La propriété DispatchRuntime vous permet également d'inspecter, de modifier ou de personnaliser le processus de distribution. Cet exemple se concentre sur la propriété InstanceProvider qui pointe sur l'objet qui fournit les instances de la classe de service.

IInstanceProvider

Dans WCF, le répartiteur crée des instances de la classe de service à l'aide d'un InstanceProvider, qui implémente l'interface IInstanceProvider. Cette interface a trois méthodes :

  • GetInstance : lorsqu'un message arrive, le répartiteur appelle la méthode GetInstance afin de créer une instance de la classe de service pour traiter le message. La fréquence des appels à cette méthode est déterminée par la propriété InstanceContextMode. Par exemple, si la propriété InstanceContextMode a la valeur PerCall, une nouvelle instance de classe de service est créée pour traiter chaque message qui arrive, et GetInstance est donc appelé chaque fois qu'un message arrive.
  • GetInstance : identique à la méthode précédente, excepté que celle-ci est appelée lorsqu'il n'y a pas d'argument Message.
  • ReleaseInstance : lorsque la durée de vie d'une instance de service a expiré, le répartiteur appelle la méthode ReleaseInstance. À l'instar de la méthode GetInstance, la fréquence des appels à cette méthode est déterminée par la propriété InstanceContextMode.

Pool d'objets

Une implémentation IInstanceProvider personnalisée fournit la sémantique de pool d'objet requise pour un service. Par conséquent, cet exemple a un type ObjectPoolingInstanceProvider qui fournit une implémentation personnalisée de IInstanceProvider pour le pool. Lorsque Dispatcher appelle la méthode GetInstance, au lieu de créer une instance, l'implémentation personnalisée recherche un objet existant dans un pool en mémoire. Si aucun n'est disponible, il est retourné. Sinon, un nouvel objet est créé. L'implémentation pour GetInstance est présentée dans l'exemple de code suivant.

object IInstanceProvider.GetInstance(InstanceContext instanceContext, Message message)
{
    object obj = null;

    lock (poolLock)
    {
        if (pool.Count > 0)
        {
            obj = pool.Pop();
        }
        else
        {
            obj = CreateNewPoolObject();
        }
        activeObjectsCount++;
    }

    WritePoolMessage(ResourceHelper.GetString("MsgNewObject"));

    idleTimer.Stop();

    return obj;          
}

L'implémentation ReleaseInstance personnalisée ajoute de nouveau l'instance libérée au pool et décrémente la valeur ActiveObjectsCount. Dispatcher peut appeler ces méthodes à partir de différents threads et, par conséquent, l'accès synchronisé aux membres au niveau de la classe dans la classe ObjectPoolingInstanceProvider est requis.

void IInstanceProvider.ReleaseInstance(InstanceContext instanceContext, object instance)
{
    lock (poolLock)
    {
        pool.Push(instance);
        activeObjectsCount--;

        WritePoolMessage(
        ResourceHelper.GetString("MsgObjectPooled"));

        // When the service goes completely idle (no requests 
        // are being processed), the idle timer is started
        if (activeObjectsCount == 0)
            idleTimer.Start();                     
    }
}

La méthode ReleaseInstance fournit une fonctionnalité « initialisation du nettoyage ». Normalement, le pool gère un nombre minimal d'objets pendant sa durée de vie. Toutefois, il peut y avoir des périodes d'utilisation excessive qui requièrent la création d'objets supplémentaires dans le pool afin d'atteindre la limite maximale spécifiée dans la configuration. Par la suite, lorsque le pool devient moins actif, ces objets en surplus peuvent devenir une surcharge supplémentaire. Par conséquent, lorsque activeObjectsCount atteint zéro, une minuterie d'inactivité est démarrée, puis déclenche et effectue un cycle de nettoyage.

Ajout du comportement

Les extensions de couche de répartiteur sont raccordées à l'aide des comportements suivants :

  • Comportements de service : permettent de personnaliser l'ensemble de l'exécution du service.
  • Comportements de point de terminaison : permettent de personnaliser les points de terminaison de service, en particulier un répartiteur de canal et de point de terminaison.
  • Comportements de contrat : permettent de personnaliser les classes ClientRuntime et DispatchRuntime sur le client et le service, respectivement.

Dans le cadre d'une extension de pool d'objets, vous devez créer un comportement de service. Pour ce faire, implémentez l'interface IServiceBehavior. Il existe plusieurs méthodes pour que le modèle de service tienne compte des comportements personnalisés :

  • Utilisation d'un attribut personnalisé.
  • L'ajouter de façon impérative à la collection de comportements de la description de service.
  • Extension du fichier de configuration

Cet exemple utilise un attribut personnalisé. Lorsque ServiceHost est construit, il examine les attributs utilisés dans la définition de type du service et ajoute les comportements disponibles à la collection de comportements de la description de service.

L'interface IServiceBehavior a trois méthode : Validate, AddBindingParameters et ApplyDispatchBehavior. La méthode Validate permet de s'assurer que le comportement peut être appliqué au service. Dans cet exemple, l'implémentation garantit que le service n'est pas configuré avec Single. La méthode AddBindingParameters permet de configurer les liaisons du service. Elle n'est pas requise dans ce scénario. ApplyDispatchBehavior permet de configurer les répartiteurs du service. Cette méthode est appelée par WCF lors de l'initialisation de ServiceHost. Les paramètres suivants sont passés dans cette méthode :

  • Description : cet argument fournit la description de service pour l'ensemble du service. Il permet d'inspecter les données de description sur les points de terminaison, les contrats, les liaisons et autres données du service.
  • ServiceHostBase  cet argument fournit le ServiceHostBase actuellement initialisé.

Dans l'implémentation IServiceBehavior personnalisée, une nouvelle instance de ObjectPoolingInstanceProvider est instanciée et assignée à la propriété InstanceProvider dans chaque DispatchRuntime de ServiceHostBase.

void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
    // Create an instance of the ObjectPoolInstanceProvider.
    ObjectPoolingInstanceProvider instanceProvider = new
           ObjectPoolingInstanceProvider(description.ServiceType, 
                                                    minPoolSize);

    // Forward the call if we created a ServiceThrottlingBehavior.
    if (this.throttlingBehavior != null)
    {
        ((IServiceBehavior)this.throttlingBehavior).ApplyDispatchBehavior(description, serviceHostBase);
    }

    // In case there was already a ServiceThrottlingBehavior 
    // (this.throttlingBehavior==null), it should have initialized 
    // a single ServiceThrottle on all ChannelDispatchers.  
    // As we loop through the ChannelDispatchers, we verify that 
    // and modify the ServiceThrottle to guard MaxPoolSize.
    ServiceThrottle throttle = null;

    foreach (ChannelDispatcherBase cdb in 
            serviceHostBase.ChannelDispatchers)
    {
        ChannelDispatcher cd = cdb as ChannelDispatcher;
        if (cd != null)
        {
            // Make sure there is exactly one throttle used by all 
            // endpoints. If there were others, we could not enforce 
            // MaxPoolSize.
            if ((this.throttlingBehavior == null) && 
                        (this.maxPoolSize != Int32.MaxValue))
            {
                if (throttle == null)
                {
                    throttle = cd.ServiceThrottle;
                }
                if (cd.ServiceThrottle == null)
                {
                    throw new 
InvalidOperationException(ResourceHelper.GetString("ExNullThrottle"));
                }
                if (throttle != cd.ServiceThrottle)
                {
                    throw new InvalidOperationException(ResourceHelper.GetString("ExDifferentThrottle"));
                }
             }

             foreach (EndpointDispatcher ed in cd.Endpoints)
             {
                 // Assign it to DispatchBehavior in each endpoint.
                 ed.DispatchRuntime.InstanceProvider = 
                                      instanceProvider;
             }
         }
     }

     // Set the MaxConcurrentInstances to limit the number of items 
     // that will ever be requested from the pool.
     if ((throttle != null) && (throttle.MaxConcurrentInstances > 
                                      this.maxPoolSize))
     {
         throttle.MaxConcurrentInstances = this.maxPoolSize;
     }
}

Outre une implémentation IServiceBehavior, la classe ObjectPoolingAttribute a plusieurs membres pour personnaliser le pool d'objets à l'aide des arguments d'attribut. Ces membres incluent MaxPoolSize, MinPoolSize et CreationTimeout, pour faire correspondre le jeu de fonctionnalités de pool d'objets fourni par .NET Enterprise Services.

Le comportement de pool d'objets peut maintenant être ajouté à un service WCF en annotant l'implémentation de service avec l'attribut ObjectPooling personnalisé récemment créé.

[ObjectPooling(MaxPoolSize=1024, MinPoolSize=10, CreationTimeout=30000)]    
public class PoolService : IPoolService
{
  // …
}

Exécution de l'exemple

L'exemple montre les avantages qui peuvent être retirés en termes de performance en utilisant le pool d'objets dans des scénarios spécifiques.

L'application de service implémente deux services : WorkService et ObjectPooledWorkService. Ces deux services partagent la même implémentation : ils requièrent tous deux une initialisation gourmande en ressources, puis exposent une méthode DoWork() qui s'avère relativement peu gourmande. La seule différence est que le pool d'objets de ObjectPooledWorkService est configuré :

[ObjectPooling(MinPoolSize = 0, MaxPoolSize = 5)]
public class ObjectPooledWorkService : IDoWork
{
    public ObjectPooledWorkService()
    {
        Thread.Sleep(5000);
        ColorConsole.WriteLine(ConsoleColor.Blue, "ObjectPooledWorkService instance created.");
    }

    public void DoWork()
    {
        ColorConsole.WriteLine(ConsoleColor.Blue, "ObjectPooledWorkService.GetData() completed.");
    }        
}

Lorsque vous exécutez le client, il chronomètre l'appel à WorkService 5 fois. Il chronomètre ensuite l'appel à ObjectPooledWorkService 5 fois. La différence de temps est ensuite affichée :

Press <ENTER> to start the client.

Calling WorkService:
1 - DoWork() Done
2 - DoWork() Done
3 - DoWork() Done
4 - DoWork() Done
5 - DoWork() Done
Calling WorkService took: 26722 ms.
Calling ObjectPooledWorkService:
1 - DoWork() Done
2 - DoWork() Done
3 - DoWork() Done
4 - DoWork() Done
5 - DoWork() Done
Calling ObjectPooledWorkService took: 5323 ms.
Press <ENTER> to exit.
ms751482.note(fr-fr,VS.90).gifRemarque :
La première fois que le client est exécuté, les deux services semblent prendre environ le même temps. Si vous ré-exécutez l'exemple, vous constatez que ObjectPooledWorkService retourne beaucoup plus rapidement car une instance de cet objet existe déjà dans le pool.

Pour configurer, générer et exécuter l'exemple

  1. Assurez-vous d'avoir effectué la procédure indiquée dans la section Procédure d'installation unique pour les exemples Windows Communication Foundation.

  2. Pour générer la solution, suivez les instructions indiquées dans Génération des exemples Windows Communication Foundation.

  3. Pour exécuter l'exemple dans une configuration à un ou plusieurs ordinateurs, suivez les instructions indiquées dans Exécution des exemples Windows Communication Foundation.

ms751482.note(fr-fr,VS.90).gifRemarque :
Si vous utilisez Svcutil.exe pour régénérer la configuration pour cet exemple, assurez-vous de modifier le nom du point de terminaison dans la configuration du client pour qu'il corresponde au code client.

Send comments about this topic to Microsoft.
© 2007 Microsoft Corporation. All rights reserved.