Benutzerdefinierte Lebensdauer

In diesem Beispiel zur Lebensdauer wird veranschaulicht, wie eine WCF-Erweiterung (Windows Communication Foundation) geschrieben wird, die benutzerdefinierte Lebensdauerdienste für freigegebene WCF-Dienstinstanzen bereitstellen soll.

Hinweis

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

Freigegebene Instanziierung

WCF stellt mehrere Instanziierungsmodi für die Dienstinstanzen bereit. Der freigegebene Instanziierungsmodus, der in diesem Artikel behandelt wird, bietet eine Möglichkeit, eine Dienstinstanz zwischen mehreren Kanälen freizugeben. Clients können eine Factorymethode im Dienst kontaktieren und einen neuen Kanal erstellen, um die Kommunikation zu starten. Im folgenden Codeausschnitt wird gezeigt, wie eine Clientanwendung einen neuen Kanal zu einer vorhandenen Dienstinstanz erstellt:

// Create a header for the shared instance id
MessageHeader shareableInstanceContextHeader = MessageHeader.CreateHeader(
        CustomHeader.HeaderName,
        CustomHeader.HeaderNamespace,
        Guid.NewGuid().ToString());

// Create the channel factory
ChannelFactory<IEchoService> channelFactory =
    new ChannelFactory<IEchoService>("echoservice");

// Create the first channel
IEchoService proxy = channelFactory.CreateChannel();

// Call an operation to create shared service instance
using (new OperationContextScope((IClientChannel)proxy))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(shareableInstanceContextHeader);
    Console.WriteLine("Service returned: " + proxy.Echo("Apple"));
}

((IChannel)proxy).Close();

// Create the second channel
IEchoService proxy2 = channelFactory.CreateChannel();

// Call an operation using the same header that will reuse the shared service instance
using (new OperationContextScope((IClientChannel)proxy2))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(shareableInstanceContextHeader);
    Console.WriteLine("Service returned: " + proxy2.Echo("Apple"));
}

Der freigegebene Instanziierungsmodus unterscheidet sich von anderen Instanziierungsmodi in seiner einzigartigen Methode zum Freigeben von Dienstinstanzen. Wenn alle Kanäle für InstanceContext geschlossen sind, überprüft die WCF-Dienstruntime, ob der Dienst InstanceContextMode für PerCall oder PerSessionkonfiguriert ist. Wenn ja, gibt sie die Instanz frei und beansprucht die Ressourcen. Wenn ein benutzerdefinierter IInstanceContextProvider verwendet wird, ruft WCF die IsIdle-Methode der Anbieterimplementierung auf, bevor die Instanz freigegeben wird. Wenn IsIdletruezuückgibt, wird die Instanz freigegeben, andernfalls ist die IInstanceContextProvider-Implementierung dafür zuständig, den Dispatcher unter Verwendung einer Rückrufmethode über den Leerlaufzustand zu benachrichtigen. Dies erfolgt durch Aufrufen der NotifyIdle-Methode des Anbieters.

In diesem Beispiel wird veranschaulicht, wie Sie die Freigabe von InstanceContext mit einem Leerlauftimeout von 20 Sekunden verzögern können.

Erweitern von InstanceContext

In WCF ist InstanceContext die Verknüpfung zwischen der Dienstinstanz und Dispatcher. WCF ermöglicht Ihnen, diese Laufzeitkomponente zu erweitern, indem sie mithilfe des erweiterbaren Objektmusters einen neuen Zustand oder ein neues Verhalten hinzufügt. Das erweiterbare Objektmuster wird in WCF verwendet, um vorhandene Laufzeitklassen um neue Funktionen zu erweitern oder um neue Zustandsfunktionen zu einem Objekt hinzuzufügen. Es gibt drei Schnittstellen im erweiterbaren Objektmuster: IExtensibleObject<T>, IExtension<T> und IExtensionCollection<T>.

Die IExtensibleObject<T>-Schnittstelle wird von Objekten implementiert, um Erweiterungen zuzulassen, die ihre Funktionalität anpassen.

Die IExtension<T>-Schnittstelle wird von Objekten implementiert, die Erweiterungen von Klassen des Typs T sein können.

Und schließlich ist die IExtensionCollection<T>-Schnittstelle eine Auflistung von IExtension<T>-Implementierungen, die das Abrufen einer Implementierung von IExtension<T> nach Typ zulässt.

Aus diesem Grund müssen Sie die IExtension<T>-Schnittstelle implementieren, um InstanceContext zu erweitern. In diesem Beispielprojekt enthält die CustomLeaseExtension-Klasse diese Implementierung.

class CustomLeaseExtension : IExtension<InstanceContext>
{
}

Die IExtension<T>-Schnittstelle verfügt über zwei Methoden: Attach und Detach. Wie ihre Namen vermuten lassen, werden diese beiden Methoden aufgerufen, wenn die Laufzeit die Erweiterung an eine Instanz der InstanceContext-Klasse anfügt oder von der Instanz löst. In diesem Beispiel wird die Attach-Methode für die Nachverfolgung des InstanceContext-Objekts verwendet, das zur aktuellen Instanz der Erweiterung gehört.

InstanceContext owner;

public void Attach(InstanceContext owner)
{
    this.owner = owner;
}

Darüber hinaus müssen Sie zur Bereitstellung der erweiterten Lebensdauerunterstützung die erforderliche Implementierung zur Erweiterung hinzufügen. Deswegen wird die ICustomLease-Schnittstelle mit den gewünschten Methoden deklariert und in der CustomLeaseExtension-Klasse implementiert.

interface ICustomLease
{
    bool IsIdle { get; }
    InstanceContextIdleCallback Callback { get; set; }
}

class CustomLeaseExtension : IExtension<InstanceContext>, ICustomLease
{
}

Wenn WCF die IsIdle-Methode in der IInstanceContextProvider-Implementierung aufruft, wird dieser Aufruf an die IsIdle-Methode der CustomLeaseExtension weitergeleitet. Dann überprüft die CustomLeaseExtension den privaten Zustand, um festzustellen, ob sich der InstanceContext im Leerlauf befindet. Wenn er sich in Leerlauf befindet, wird true zurückgegeben. Andernfalls wird ein Zeitgeber für eine bestimmte erweiterte Lebensdauerperiode gestartet.

public bool IsIdle
{
  get
  {
    lock (thisLock)
    {
      if (isIdle)
      {
        return true;
      }
      else
      {
        StartTimer();
        return false;
      }
    }
  }
}

Im Elapsed-Ereignis des Timers wird die Rückruffunktion im Dispatcher aufgerufen, um einen weiteren Bereinigungszyklus zu starten.

void idleTimer_Elapsed(object sender, ElapsedEventArgs args)
{
    lock (thisLock)
    {
        StopTimer();
        isIdle = true;
        Utility.WriteMessageToConsole(
            ResourceHelper.GetString("MsgLeaseExpired"));
        callback(owner);
    }
}

Der ausgeführte Timer kann nicht erneuert werden, wenn eine neue Nachricht für die Instanz eingeht, die in den Leerlaufzustand verschoben wird.

Im Beispiel wird IInstanceContextProvider implementiert, um die Aufrufe der IsIdle-Methode abzufangen und diese zur CustomLeaseExtension weiterzuleiten. Die IInstanceContextProvider-Implementierung ist in der CustomLifetimeLease-Klasse enthalten. Die IsIdle-Methode wird aufgerufen, wenn WCF im Begriff ist, die Dienstinstanz freizugeben. Es gibt jedoch nur eine Instanz einer bestimmten ISharedSessionInstance-Implementierung in der IInstanceContextProvider-Auflistung von ServiceBehavior. Dies bedeutet, dass nicht vorhergesagt werden kann, ob InstanceContext geschlossen ist, wenn WCF die IsIdle-Methode überprüft. Aus diesem Grund werden Anforderungen der IsIdle-Methode in diesem Beispiel mit der Threadsperre serialisiert.

Wichtig

Die Verwendung der Threadsperre wird nicht empfohlen, da die Leistung der Anwendung durch die Serialisierung deutlich beeinträchtigt werden kann.

Ein privates Memberfeld wird in der CustomLifetimeLease-Klasse verwendet, um den Leerlaufzustand nachzuverfolgen, und wird von der IsIdle-Methode zurückgegeben. Jedes Mal, wenn die IsIdle-Methode aufgerufen wird, wird das Feld isIdle zurückgegeben und auf falsezurückgesetzt. Dieser Wert muss auf false festgelegt werden, um sicherzustellen, dass der Verteiler die NotifyIdle-Methode aufruft.

public bool IsIdle(InstanceContext instanceContext)
{
    get
    {
        lock (thisLock)
        {
            //...
            bool idleCopy = isIdle;
            isIdle = false;
            return idleCopy;
        }
    }
}

Wenn die IInstanceContextProvider.IsIdle-Methode false zurückgibt, registriert der Dispatcher mithilfe der NotifyIdle-Methode eine Rückruffunktion. Diese Methode empfängt einen Verweis darauf, dass InstanceContext freigegeben wird. Daher kann der Beispielcode die ICustomLease-Typerweiterung abfragen und die ICustomLease.IsIdle Eigenschaft im erweiterten Zustand überprüfen.

public void NotifyIdle(InstanceContextIdleCallback callback,
            InstanceContext instanceContext)
{
    lock (thisLock)
    {
       ICustomLease customLease =
           instanceContext.Extensions.Find<ICustomLease>();
       customLease.Callback = callback;
       isIdle = customLease.IsIdle;
       if (isIdle)
       {
             callback(instanceContext);
       }
    }
}

Bevor die ICustomLease.IsIdle-Eigenschaft überprüft wird, muss die Rückrufeigenschaft festgelegt werden, damit die CustomLeaseExtension den Dispatcher benachrichtigen kann, sobald sie in den Leerlauf wechselt. Wenn ICustomLease.IsIdletrue zurückgibt, wird der private isIdle-Member einfach in CustomLifetimeLease auf true festgelegt, und die Rückrufmethode wird vom Member aufgerufen. Da im Code eine Sperre enthalten ist, können andere Threads den Wert dieses privaten Members nicht ändern. Und wenn Dispatcher das nächste Mal IInstanceContextProvider.IsIdle aufruft, wird true zurückgegeben, und der Dispatcher kann die Instanz freigeben.

Jetzt da die Vorarbeit für die benutzerdefinierte Erweiterung abgeschlossen ist, muss sie in das Dienstmodell eingebunden werden. Um die CustomLeaseExtension-Implementierung in den InstanceContext einzubinden, stellt WCF die IInstanceContextInitializer-Schnittstelle bereit, um das Bootstrapping von InstanceContext auszuführen. In diesem Beispiel implementiert die CustomLeaseInitializer-Klasse diese Schnittstelle und fügt eine CustomLeaseExtension-Instanz zur Extensions-Auflistung aus der einzigen Methodeninitialisierung hinzu. Diese Methode wird vom Verteiler aufgerufen, während InstanceContext initialisiert wird.

public void InitializeInstanceContext(InstanceContext instanceContext,
    System.ServiceModel.Channels.Message message, IContextChannel channel)

    //...

    IExtension<InstanceContext> customLeaseExtension =
        new CustomLeaseExtension(timeout, headerId);
    instanceContext.Extensions.Add(customLeaseExtension);
}

Schließlich wird die IInstanceContextProvider-Implementierung anhand der IServiceBehavior-Implementierung in das Dienstmodell eingebunden. Diese Implementierung wird in der CustomLeaseTimeAttribute-Klasse eingefügt, und sie wird außerdem von der Attribute-Basisklasse abgeleitet, um dieses Verhalten als Attribut verfügbar zu machen.

public void ApplyDispatchBehavior(ServiceDescription description,
           ServiceHostBase serviceHostBase)
{
    CustomLifetimeLease customLease = new CustomLifetimeLease(timeout);

    foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
    {
        ChannelDispatcher cd = cdb as ChannelDispatcher;

        if (cd != null)
        {
            foreach (EndpointDispatcher ed in cd.Endpoints)
            {
                ed.DispatchRuntime.InstanceContextProvider = customLease;
            }
        }
    }
}

Dieses Verhalten kann einer Beispieldienstklasse hinzugefügt werden, indem es mit dem CustomLeaseTime-Attribut kommentiert wird.

[CustomLeaseTime(Timeout = 20000)]
public class EchoService : IEchoService
{
  //…
}

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. Um die C#- oder Visual Basic .NET-Edition der Projektmappe zu erstellen, befolgen Sie die unter Building the Windows Communication Foundation Samplesaufgeführten Anweisungen.

  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.