Initialisierung der Instanziierung

In diesem Initialisierungsbeispiel wird das Beispiel für das Pooling erweitert, indem die Schnittstelle IObjectControl definiert wird, die die Initialisierung eines Objekts anpasst, indem es aktiviert und deaktiviert wird. Der Client ruft Methoden auf, die das Objekt an den Pool zurückgeben und das Objekt nicht an den Pool zurückgeben.

Hinweis

Die Setupprozedur und die Buildanweisungen für dieses Beispiel befinden sich am Ende dieses Themas.

Erweiterungspunkte

Der erste Schritt beim Erstellen einer Windows Communication Foundation-Erweiterung (WCF) besteht darin, den zu verwendenden Erweiterungspunkt auszuwählen. In WCF bezeichnet der Begriff EndpointDispatcher eine Laufzeitkomponente, die eingehende Nachrichten in Methodenaufrufe für den Dienst des Benutzers konvertiert und Rückgabewerte von dieser Methode in eine ausgehende Nachricht konvertiert. Ein WCF-Dienst erstellt einen EndpointDispatcher für jeden Endpunkt.

Der EndpointDispatcher stellt mithilfe der EndpointDispatcher-Klasse die Erweiterung des Endpunktbereichs bereit (für alle vom Dienst empfangenen oder gesendeten Nachrichten). Mit dieser Klasse können Sie verschiedene Eigenschaften anpassen, die das Verhalten von EndpointDispatcher steuern. In diesem Beispiel wird in erster Linie die InstanceProvider-Eigenschaft behandelt, die auf das Objekt zeigt, das die Instanzen der Dienstklasse bereitstellt.

IInstanceProvider

In WCF erstellt EndpointDispatcher mithilfe eines Instanzenanbieters, der die IInstanceProvider-Schnittstelle implementiert, Instanzen einer Dienstklasse. Diese Schnittstelle verfügt über nur zwei Methoden:

  • GetInstance: Wenn eine Nachricht eingeht, ruft der Verteiler die GetInstance-Methode auf, um eine Instanz der Dienstklasse zum Verarbeiten der Nachricht zu erstellen. Die Häufigkeit der Aufrufe dieser Methode wird von der InstanceContextMode-Eigenschaft bestimmt. Wenn die InstanceContextMode-Eigenschaft beispielsweise auf InstanceContextMode.PerCall festgelegt ist, wird eine neue Instanz der Dienstklasse erstellt, um alle eingehenden Nachrichten zu verarbeiten. Daher wird GetInstance immer dann aufgerufen, wenn eine Nachricht eingeht.

  • ReleaseInstance: Wenn die Dienstinstanz das Verarbeiten der Nachricht abgeschlossen hat, ruft EndpointDispatcher die ReleaseInstance-Methode auf. Wie bei der GetInstance-Methode wird die Häufigkeit der Aufrufe dieser Methode von der InstanceContextMode-Eigenschaft bestimmt.

Der Objektpool

Die ObjectPoolInstanceProvider-Klasse enthält die Implementierung des Objektpools. Diese Klasse implementiert die IInstanceProvider-Schnittstelle für die Interaktion mit der Dienstmodellebene. Wenn EndpointDispatcher die GetInstance-Methode aufruft, erstellt die benutzerdefinierte Implementierung keine neue Instanz, sondern sucht ein vorhandenes Objekt in einem Pool im Speicher. Wenn eines verfügbar ist, wird es zurückgegeben. Andernfalls überprüft ObjectPoolInstanceProvider, ob die ActiveObjectsCount-Eigenschaft (Anzahl der aus dem Pool zurückgegebenen Objekte) die maximale Poolgröße erreicht hat. Wenn dies nicht der Fall ist, wird eine neue Instanz erstellt und an den Aufrufer zurückgegeben, und anschließend wird ActiveObjectsCount inkrementiert. Andernfalls wird eine Objekterstellungsanforderung für einen konfigurierten Zeitraum in die Warteschlange gestellt. Die Implementierung für GetObjectFromThePool wird im folgenden Beispielcode dargestellt.

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

Die benutzerdefinierte ReleaseInstance-Implementierung fügt die freigegebene Instanz dem Pool erneut hinzu und dekrementiert den ActiveObjectsCount-Wert. EndpointDispatcher kann diese Methoden von unterschiedlichen Threads aus aufrufen. Daher ist ein synchronisierter Zugriff auf die Member der Klassenebene in der ObjectPoolInstanceProvider-Klasse erforderlich.

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

Die ReleaseInstance-Methode stellt eine Funktion zur Bereinigungsinitialisierung bereit. Normalerweise wird im Pool eine Mindestanzahl von Objekten für die Lebensdauer des Pools beibehalten. Es kann jedoch Zeiten mit übermäßiger Auslastung geben, für die im Pool zusätzliche Objekte erstellt werden müssen, um die in der Konfiguration festgelegte Höchstgrenze zu erreichen. Wenn der Pool weniger aktiv ist, stellen diese überzähligen Objekte einen zusätzlichen Aufwand dar. Wenn activeObjectsCount daher 0 (null) erreicht, wird ein Leerlaufzeitgeber gestartet, der einen Bereinigungszyklus auslöst und ausführt.

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

ServiceModel-Ebenenerweiterungen werden mithilfe der folgenden Verhalten verknüpft:

  • Dienstverhalten: Diese ermöglichen die Anpassung der ganzen Dienstlaufzeit.

  • Endpunktverhalten: Diese ermöglichen das Anpassen eines bestimmten Dienstendpunkts, einschließlich EndpointDispatcher.

  • Vertragsverhalten: Diese ermöglichen das Anpassen von ClientRuntime-Klassen oder DispatchRuntime-Klassen auf dem Client bzw. Server.

  • Vorgangsverhalten: Diese ermöglichen das Anpassen von ClientOperation-Klassen oder DispatchOperation-Klassen auf dem Client bzw. Server.

Für den Zweck einer Objektpoolingerweiterung kann ein Endpunktverhalten oder ein Dienstverhalten erstellt werden. In diesem Beispiel wird ein Dienstverhalten verwendet, das die Objektpoolingfähigkeit auf alle Endpunkte des Diensts anwendet. Dienstverhaltensweisen werden durch Implementieren der IServiceBehavior-Schnittstelle erstellt. Es gibt mehrere Möglichkeiten, ServiceModel auf die benutzerdefinierten Verhaltensweisen hinzuweisen:

  • Verwenden eines benutzerdefinierten Attributs.

  • Dieses wird der Verhaltensauflistung der Dienstbeschreibung imperativ hinzugefügt.

  • Erweitern der Konfigurationsdatei.

In diesem Beispiel wird ein benutzerdefiniertes Attribut verwendet. Beim Erstellen von ServiceHost werden die in der Typdefinition des Diensts verwendeten Attribute untersucht, und die verfügbaren Verhalten werden der Verhaltensauflistung der Dienstbeschreibung hinzugefügt.

Die IServiceBehavior-Schnittstelle verfügt über drei Methoden: Validate,AddBindingParameters, und ApplyDispatchBehavior. Diese Methoden werden von WCF aufgerufen, wenn ServiceHost initialisiert wird. IServiceBehavior.Validate wird zuerst aufgerufen. So kann der Dienst auf Inkonsistenzen untersucht werden. IServiceBehavior.AddBindingParameters wird danach aufgerufen. Diese Methode ist nur in sehr komplexen Szenarios erforderlich. IServiceBehavior.ApplyDispatchBehavior wird zuletzt aufgerufen und ist für das Konfigurieren der Laufzeit zuständig. Die folgenden Parameter werden in IServiceBehavior.ApplyDispatchBehavior übergeben:

  • Description: Dieser Parameter stellt die Dienstbeschreibung für den gesamten Dienst bereit. Dieser kann verwendet werden, um Beschreibungsdaten über die Endpunkte, Verträge, Bindungen und andere Daten zum Dienst zu überprüfen.

  • ServiceHostBase: Dieser Parameter stellt die ServiceHostBase bereit, die gerade initialisiert wird.

In der benutzerdefinierten IServiceBehavior-Implementierung wird eine neue Instanz von ObjectPoolInstanceProvider instanziiert und der InstanceProvider-Eigenschaft in jedem EndpointDispatcher zugewiesen, der ServiceHostBase angefügt ist.

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

Neben einer IServiceBehavior-Implementierung verfügt die ObjectPoolingAttribute-Klasse über mehrere Member zum Anpassen des Objektpools mithilfe der Attributargumente. Diese Member umfassen MaxSize, MinSize, Enabled und CreationTimeout für die Übereinstimmung mit dem von .NET Enterprise Services bereitgestellten Objektpooling-Funktionssatz.

Das Objektpoolingverhalten kann nun einem WCF-Dienst hinzugefügt werden, indem der Dienstimplementierung mit dem neu erstellten, benutzerdefinierten ObjectPooling-Attribut eine Anmerkung hinzugefügt wird.

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

Aktivieren und Deaktivieren von Verknüpfungen

Das primäre Ziel des Objektpoolings ist das Optimieren von Objekten mit kurzer Lebensdauer und relativ aufwändiger Erstellung und Initialisierung. Bei der richtigen Verwendung kann daher eine erhebliche Leistungssteigerung erreicht werden. Da das Objekt aus dem Pool zurückgegeben wird, wird der Konstruktor nur einmal aufgerufen. Bei einigen Anwendungen ist jedoch eine gewisse Kontrolle erforderlich, damit sie die in einem einzigen Kontext verwendeten Ressourcen initialisieren und bereinigen können. Ein Objekt, das beispielsweise für eine Gruppe von Berechnungen verwendet wird, kann die privaten Felder zurücksetzen, bevor die nächste Berechnung verarbeitet wird. In Enterprise Services wurde diese Art der kontextspezifischen Initialisierung ermöglicht, indem der Objektentwickler die Activate-Methode und die Deactivate-Methode in der ServicedComponent-Basisklasse überschreiben konnte.

Der Objektpool ruft die Activate-Methode unmittelbar vor dem Zurückgeben des Objekts aus dem Pool auf. Deactivate wird aufgerufen, wenn das Objekt an den Pool zurückgegeben wird. Die ServicedComponent-Basisklasse verfügt außerdem über die boolean-Eigenschaft mit der Bezeichnung CanBePooled, mit der der Pool darüber benachrichtigt werden kann, ob das Objekt weiter gepoolt werden kann.

Im Beispiel wird eine öffentliche Schnittstelle (IObjectControl) deklariert, die über die oben genannten Member verfügt, um diese Funktionalität zu imitieren. Diese Schnittstelle wird dann von Dienstklassen implementiert, die die kontextspezifische Initialisierung bereitstellen sollen. Die IInstanceProvider-Implementierung muss geändert werden, um diese Anforderungen zu erfüllen. Wenn Sie nun ein Objekt durch Aufrufen der GetInstance-Methode abrufen, müssen Sie jedes Mal überprüfen, ob das Objekt IObjectControl. implementiert. Wenn dies der Fall ist, müssen Sie die Activate-Methode entsprechend aufrufen.

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

Beim Zurückgeben eines Objekts an den Pool ist eine Überprüfung für die CanBePooled-Eigenschaft erforderlich, bevor das Objekt erneut dem Pool hinzugefügt wird.

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

Da der Dienstentwickler entscheiden kann, ob ein Objekt gepoolt werden kann, kann die Objektanzahl im Pool zu einem bestimmten Zeitpunkt unter dem Mindestwert liegen. Sie müssen daher überprüfen, ob die Objektanzahl unter dem Mindestwert liegt, und die erforderliche Initialisierung in der Bereinigungsprozedur ausführen.

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

Wenn Sie das Beispiel ausführen, werden die Anforderungen und Antworten für den Vorgang im Dienst- und Clientkonsolenfenster angezeigt. Drücken Sie die EINGABETASTE in den einzelnen Konsolenfenstern, um den Dienst und den Client zu schließen.

So können Sie das Beispiel einrichten, erstellen und ausführen

  1. Stellen Sie sicher, dass Sie die Beispiele zum einmaligen Setupverfahren für Windows Communication Foundation ausgeführt haben.

  2. Befolgen Sie zum Erstellen der Projektmappe die Anweisungen unter Erstellen der Windows Communication Foundation-Beispiele.

  3. Wenn Sie das Beispiel in einer Konfiguration mit einem Computer oder über Computer hinweg ausführen möchten, folgen Sie den Anweisungen unter Durchführen der Windows Communication Foundation-Beispiele.