Pooling

In questo esempio viene illustrato come estendere Windows Communication Foundation (WCF) per supportare i pool di oggetti. L'esempio illustra come creare un attributo sintatticamente e semanticamente simile alla funzionalità dell'attributo ObjectPoolingAttribute di Enterprise Services. Il pool degli oggetti può fornire una spinta notevole alle prestazioni di un'applicazione. Tuttavia, può avere l'effetto contrario se non utilizzato correttamente. Il pool degli oggetti consente di ridurre il sovraccarico dovuto alla creazione continua di oggetti frequentemente utilizzati che richiedono un'inizializzazione estesa. Tuttavia, se una chiamata a un metodo su un oggetto del pool richiede una quantità considerevole di tempo, il pool degli oggetti mette in coda richieste aggiuntive appena viene raggiunta la dimensione del pool massima. Pertanto può non riuscire a soddisfare richieste di creazione di oggetti generando un'eccezione di timeout.

Nota

La procedura di installazione e le istruzioni di generazione per questo esempio si trovano alla fine di questo argomento.

Il primo passaggio nel creare un'estensione di WCF è decidere il punto di estensibilità da utilizzare.

In WCF il termine dispatcher fa riferimento a un componente runtime responsabile della conversione dei messaggi in arrivo in chiamate al metodo sul servizio dell'utente e della conversione di valori restituiti da quel metodo in un messaggio in uscita. Un servizio WCF crea un dispatcher per ogni endpoint. Un client WCF deve utilizzare un dispatcher se il contratto associato a quel client è un contratto duplex.

I dispatcher del canale e dell'endpoint offrono estensibilità sul canale e sul contratto esponendo le varie proprietà che controllano il comportamento del dispatcher. La proprietà DispatchRuntime consente inoltre di ispezionare, modificare e personalizzare la modalità di autenticazione dei certificati. Questo esempio si concentra sulla proprietà InstanceProvider che punta all'oggetto che fornisce le istanze della classe del servizio.

Provider di istanze

In WCF, il dispatcher crea istanze della classe del servizio utilizzando una proprietà InstanceProviderche implementa l'interfaccia IInstanceProvider. Questa interfaccia ha tre metodi:

  • GetInstance: quando un messaggio arriva il dispatcher chiama il metodo GetInstance per creare un'istanza della classe del servizio al fine di elaborare il messaggio. La frequenza delle chiamate a questo metodo è determinata dalla proprietà InstanceContextMode. Ad esempio, se la proprietà InstanceContextMode è impostata su PerCall viene creata una nuova istanza della classe del servizio per elaborare ogni messaggio che arriva, pertanto il metodo GetInstance viene chiamato ogni qualvolta arriva un messaggio.
  • GetInstance: questo metodo è identico a quello precedente, salvo che viene richiamato quando non c'è nessun argomento di messaggio.
  • ReleaseInstance: Quando la durata di un'istanza del servizio è scaduta, il dispatcher chiama il metodo ReleaseInstance. Solo per il metodo GetInstance, la frequenza delle chiamate a questo metodo è determinata dalla proprietà InstanceContextMode.

Pool di oggetti

Un'implementazione personalizzata della classe IInstanceProvider fornisce la semantica del pool di oggetti necessaria per un servizio. Pertanto, questo esempio ha un tipo ObjectPoolingInstanceProvider che fornisce un'implementazione personalizzata della classe IInstanceProvider per il pool. Quando Dispatcher chiama il metodo GetInstance, anziché creare una nuova istanza, l'implementazione personalizzata cerca un oggetto esistente in un pool in memoria che viene restituito se disponibile. In caso contrario, viene creato un nuovo oggetto. Nell'esempio di codice seguente viene illustrata l'implementazione di GetInstance.

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'implementazione personalizzata di ReleaseInstance aggiunge l'istanza rilasciata nuovamente al pool e decrementa il valore di ActiveObjectsCount. IlDispatcher può chiamare questi metodi da thread diversi e pertanto sincronizzare l'accesso ai membri del livello della classe, nella classe ObjectPoolingInstanceProvider obbligatoria.

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

Il metodo ReleaseInstance fornisce una funzionalità di "inizializzazione di pulitura". Normalmente il pool gestisce un numero minimo di oggetti per la durata del pool. Ci possono essere, tuttavia, periodi di utilizzo eccessivo che richiedono la creazione di oggetti aggiuntivi nel pool per raggiungere il limite massimo specificato nella configurazione. Successivamente, quando il pool diviene meno attivo, quegli oggetti in surplus possono divenire un sovraccarico aggiuntivo. Pertanto, quando il conteggio activeObjectsCount si azzera, viene avviato un timer, precedentemente inattivo, che inizia ed esegue un ciclo di pulizia.

Aggiunta del comportamento.

Le estensioni del livello del dispatcher vengono collegate utilizzando i comportamenti seguenti:

  • Comportamenti del servizio. Essi consentono la personalizzazione del runtime dell'intero servizio.
  • Comportamenti dell'endpoint. Essi consentono la personalizzazione degli endpoint del servizio, in particolare un canale e un dispatcher dell'endpoint.
  • Comportamenti del contratto. Essi consentono la personalizzazione di entrambe le classi ClientRuntime e DispatchRuntime rispettivamente sul client e sui servizi.

Allo scopo di un'estensione del pool di oggetti deve essere creato un comportamento del servizio. I comportamenti dei servizi vengono creati implementando l'interfaccia IServiceBehavior. Ci sono molti modi per rendere consapevole il modello dei servizi dei comportamenti personalizzati:

  • Utilizzo di un attributo personalizzato
  • Aggiunto imperativamente all'insieme di comportamenti della descrizione del servizio.
  • Estensione dei file di configurazione

Questo esempio utilizza un attributo personalizzato. Quando la classeServiceHost viene costruita esamina gli attributi utilizzati nella definizione del tipo del servizio e aggiunge i comportamenti disponibili all'insieme di comportamenti della descrizione del servizio.

L'interfaccia IServiceBehavior contiene tre metodi: Validate, AddBindingParameters e ApplyDispatchBehavior. Il metodo Validate viene utilizzato per assicurare che il comportamento possa essere applicato al servizio. In questo esempio, l'implementazione assicura che il servizio non sia configurato con Single. Il metodo AddBindingParameters viene utilizzato per configurare le associazioni del servizio. Non è obbligatorio in questo scenario. Il metodo ApplyDispatchBehavior viene utilizzato per configurare i dispatcher del servizio. Questo metodo viene chiamato WCF quando viene inizializzata la classe ServiceHost. I parametri seguenti vengono passati in questo metodo:

  • Description: questo argomento fornisce la descrizione del servizio per l'intero servizio. Può essere utilizzato per controllare dati della descrizione sugli endpoint del servizio, contratti, associazioni e altri dati.
  • ServiceHostBase: questo argomento fornisce la classe ServiceHostBase che si sta inizializzando attualmente.

Nell'implementazione personalizzata della classe IServiceBehavior viene creata una nuova istanza di ObjectPoolingInstanceProvider e viene assegnata alla proprietà InstanceProvider in ogni DispatchRuntime in 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;
     }
}

Oltre a un'implementazione della classe IServiceBehavior, la classe ObjectPoolingAttribute ha molti membri per personalizzare il pool di oggetti utilizzando gli argomenti dell'attributo. Questi membri includono MaxPoolSize, MinPoolSizee CreationTimeout, per corrispondere al set di funzionalità del pool di oggetti fornito da Enterprise Services .NET.

Il comportamento del pool di oggetti può ora essere aggiunto a un servizio WCF annotando l'implementazione del servizio con l'attributo ObjectPooling personalizzato appena creato.

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

Esecuzione dell'esempio

L'esempio illustra i vantaggi a livello di prestazioni che possono essere raggiunti utilizzando i pool di oggetti in alcuni scenari.

L'applicazione di servizio implementa due servizi: WorkService e ObjectPooledWorkService. Entrambi i servizi condividono la stessa implementazione. Richiedono entrambi una lunga inizializzazione e quindi espongono un metodo DoWork() che è relativamente conveniente. La sola differenza è che ObjectPooledWorkService ha un pool di oggetti configurato:

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

Quando si esegue il client, effettua la chiamata a WorkService 5 volte. Quindi effettua la chiamata a ObjectPooledWorkService 5 volte. Viene infine visualizzata la differenza:

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.

Nota

La prima volta che il client viene eseguito, entrambi i servizi sembrano avere la stessa durata. Se si esegue nuovamente l'esempio, è possibile vedere che ObjectPooledWorkService restituisce molto più rapidamente perché un'istanza di quell'oggetto già esiste nel pool.

Per impostare, compilare ed eseguire l'esempio

  1. Assicurarsi di avere eseguito Procedura di installazione singola per gli esempi di Windows Communication Foundation.

  2. Per generare la soluzione, seguire le istruzioni in Generazione degli esempi Windows Communication Foundation.

  3. Per eseguire l'esempio su una configurazione con un solo computer o tra computer diversi, seguire le istruzioni in Esecuzione degli esempi di Windows Communication Foundation.

Nota

Se si utilizza Svcutil.exe per rigenerare la configurazione di questo esempio, assicurarsi di modificare il nome dell'endpoint nella configurazione client in modo che corrisponda al codice client.

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