In een groep plaatsen
Het poolvoorbeeld laat zien hoe u WCF (Windows Communication Foundation) kunt uitbreiden ter ondersteuning van objectpooling. In het voorbeeld ziet u hoe u een kenmerk maakt dat syntactisch en semantisch vergelijkbaar is met de ObjectPoolingAttribute
kenmerkfunctionaliteit van Enterprise Services. Objectpooling kan de prestaties van een toepassing aanzienlijk verbeteren. Het kan echter het tegenovergestelde effect hebben als het niet goed wordt gebruikt. Objectpooling vermindert de overhead van het opnieuw maken van veelgebruikte objecten waarvoor uitgebreide initialisatie is vereist. Als het echter veel tijd kost om een aanroep naar een methode op een gegroepeerd object te voltooien, worden extra aanvragen bij objectpooling in wachtrijen geplaatst zodra de maximale poolgrootte is bereikt. Het kan dus mislukken om bepaalde aanvragen voor het maken van objecten te verwerken door een time-outuitzondering te genereren.
Notitie
De installatieprocedure en build-instructies voor dit voorbeeld bevinden zich aan het einde van dit onderwerp.
De eerste stap bij het maken van een WCF-extensie is het bepalen van het uitbreidbaarheidspunt dat moet worden gebruikt.
In WCF verwijst de term dispatcher naar een runtimeonderdeel dat verantwoordelijk is voor het converteren van binnenkomende berichten naar methodeaanroepen op de service van de gebruiker en voor het converteren van retourwaarden van die methode naar een uitgaand bericht. Een WCF-service maakt een dispatcher voor elk eindpunt. Een WCF-client moet een dispatcher gebruiken als het contract dat aan die klant is gekoppeld een dubbelzijdig contract is.
De kanaal- en eindpuntverzenders bieden kanaal- en contractbreidbaarheid door verschillende eigenschappen weer te geven waarmee het gedrag van de dispatcher wordt bepaald. Met DispatchRuntime de eigenschap kunt u ook het verzendingsproces inspecteren, wijzigen of aanpassen. Dit voorbeeld is gericht op de InstanceProvider eigenschap die verwijst naar het object dat de exemplaren van de serviceklasse levert.
De IInstanceProvider
In WCF maakt de dispatcher exemplaren van de serviceklasse met behulp van een InstanceProvider, waarmee de IInstanceProvider interface wordt geïmplementeerd. Deze interface heeft drie methoden:
GetInstance(InstanceContext, Message): Wanneer er een bericht binnenkomt, wordt de GetInstance(InstanceContext, Message) methode aangeroepen om een exemplaar van de serviceklasse te maken om het bericht te verwerken. De frequentie van de aanroepen naar deze methode wordt bepaald door de InstanceContextMode eigenschap. Als de InstanceContextMode eigenschap bijvoorbeeld is ingesteld op PerCall een nieuw exemplaar van serviceklasse, wordt gemaakt om elk bericht te verwerken dat binnenkomt, dus GetInstance(InstanceContext, Message) wanneer een bericht binnenkomt.
GetInstance(InstanceContext): Dit is identiek aan de vorige methode, behalve dat dit wordt aangeroepen wanneer er geen berichtargument is.
ReleaseInstance(InstanceContext, Object): Wanneer de levensduur van een service-exemplaar is verstreken, roept dispatcher de ReleaseInstance(InstanceContext, Object) methode aan. Net als bij de GetInstance(InstanceContext, Message) methode wordt de frequentie van de aanroepen naar deze methode bepaald door de InstanceContextMode eigenschap.
De objectgroep
Een aangepaste IInstanceProvider implementatie biedt de vereiste semantiek voor objectpooling voor een service. Daarom heeft dit voorbeeld een ObjectPoolingInstanceProvider
type dat aangepaste implementatie van IInstanceProvider pooling biedt. Wanneer de Dispatcher
GetInstance(InstanceContext, Message) methode wordt aangeroepen in plaats van een nieuw exemplaar te maken, zoekt de aangepaste implementatie naar een bestaand object in een in-memory pool. Als er een beschikbaar is, wordt deze geretourneerd. Anders wordt er een nieuw object gemaakt. De implementatie voor GetInstance
wordt weergegeven in de volgende voorbeeldcode.
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;
}
Met de aangepaste implementatie wordt het uitgebrachte ReleaseInstance
exemplaar weer toegevoegd aan de pool en wordt de ActiveObjectsCount
waarde gedegraded. Deze Dispatcher
methoden kunnen worden aangeroepen vanuit verschillende threads en daarom is gesynchroniseerde toegang tot de leden op klasseniveau in de ObjectPoolingInstanceProvider
klasse vereist.
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();
}
}
De ReleaseInstance
methode biedt een functie voor het opschonen van initialisatie. Normaal gesproken onderhoudt de pool een minimum aantal objecten voor de levensduur van de pool. Er kunnen echter perioden zijn van overmatig gebruik waarvoor het maken van extra objecten in de groep nodig is om de maximale limiet te bereiken die is opgegeven in de configuratie. Uiteindelijk, wanneer de pool minder actief wordt, kunnen deze overschotobjecten een extra overhead worden. Wanneer de activeObjectsCount
nul bereikt, wordt daarom een niet-actieve timer gestart die wordt geactiveerd en een opschooncyclus wordt uitgevoerd.
Het gedrag toevoegen
Extensies voor dispatcherlagen worden gekoppeld met behulp van het volgende gedrag:
Servicegedrag. Hierdoor kan de volledige serviceruntime worden aangepast.
Eindpuntgedrag. Hiermee kunt u service-eindpunten aanpassen, met name een kanaal- en eindpunt-dispatcher.
Contractgedrag. Hierdoor kunnen zowel ClientRuntimeDispatchRuntime als klassen op de client en de service worden aangepast.
Voor het doel van een objectgroeperingsextensie moet een servicegedrag worden gemaakt. Servicegedrag wordt gemaakt door de IServiceBehavior interface te implementeren. Er zijn verschillende manieren om het servicemodel bewust te maken van het aangepaste gedrag:
Een aangepast kenmerk gebruiken.
Imperatief toevoegen aan de gedragverzameling van de servicebeschrijving.
Het configuratiebestand uitbreiden.
In dit voorbeeld wordt een aangepast kenmerk gebruikt. Wanneer de ServiceHost service is samengesteld, worden de kenmerken onderzocht die worden gebruikt in de typedefinitie van de service en worden de beschikbare gedragingen toegevoegd aan de verzameling gedrag van de servicebeschrijving.
De interface IServiceBehavior heeft drie methoden : - Validate, AddBindingParametersen ApplyDispatchBehavior. De Validate methode wordt gebruikt om ervoor te zorgen dat het gedrag op de service kan worden toegepast. In dit voorbeeld zorgt de implementatie ervoor dat de service niet is geconfigureerd met Single. De AddBindingParameters methode wordt gebruikt om de bindingen van de service te configureren. Dit is niet vereist in dit scenario. Deze ApplyDispatchBehavior wordt gebruikt om de dispatchers van de service te configureren. Deze methode wordt aangeroepen door WCF wanneer de ServiceHost wordt geïnitialiseerd. De volgende parameters worden doorgegeven aan deze methode:
Description
: Dit argument bevat de servicebeschrijving voor de hele service. Dit kan worden gebruikt om beschrijvingsgegevens over de eindpunten, contracten, bindingen en andere gegevens van de service te controleren.ServiceHostBase
: Dit argument bevat het ServiceHostBase argument dat momenteel wordt geïnitialiseerd.
In de aangepaste IServiceBehavior implementatie wordt een nieuw exemplaar van ObjectPoolingInstanceProvider
geïnstantieerd en toegewezen aan de InstanceProvider eigenschap in elk DispatchRuntime in 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))
{
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;
}
}
Naast een IServiceBehavior implementatie heeft de ObjectPoolingAttribute klasse verschillende leden om de objectgroep aan te passen met behulp van de kenmerkargumenten. Deze leden omvatten MaxPoolSize, MinPoolSizeen CreationTimeout, die overeenkomen met de functieset voor objectpooling die wordt geleverd door .NET Enterprise Services.
Het gedrag van objectpooling kan nu worden toegevoegd aan een WCF-service door aantekeningen te maken bij de service-implementatie met het zojuist gemaakte aangepaste ObjectPooling
kenmerk.
[ObjectPooling(MaxPoolSize=1024, MinPoolSize=10, CreationTimeout=30000)]
public class PoolService : IPoolService
{
// …
}
Het voorbeeld uitvoeren
In het voorbeeld ziet u de prestatievoordelen die kunnen worden verkregen met behulp van objectpooling in bepaalde scenario's.
De servicetoepassing implementeert twee services , WorkService
en ObjectPooledWorkService
. Beide services delen dezelfde implementatie. Ze vereisen allebei dure initialisatie en maken vervolgens een DoWork()
methode beschikbaar die relatief goedkoop is. Het enige verschil is dat objectpooling ObjectPooledWorkService
is geconfigureerd:
[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.");
}
}
Wanneer u de client uitvoert, wordt de WorkService
5 keer aangeroepen. Vervolgens wordt de ObjectPooledWorkService
5 keer gebeld. Het verschil in tijd wordt vervolgens weergegeven:
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.
Notitie
De eerste keer dat de client wordt uitgevoerd, lijken beide services ongeveer dezelfde tijd in beslag te nemen. Als u het voorbeeld opnieuw uitvoert, kunt u zien dat de ObjectPooledWorkService
retourneert veel sneller omdat er al een exemplaar van dat object in de pool bestaat.
Het voorbeeld instellen, compileren en uitvoeren
Zorg ervoor dat u de eenmalige installatieprocedure voor de Windows Communication Foundation-voorbeelden hebt uitgevoerd.
Volg de instructies in Het bouwen van de Windows Communication Foundation-voorbeelden om de oplossing te bouwen.
Als u het voorbeeld wilt uitvoeren in een configuratie met één of meerdere computers, volgt u de instructies in Het uitvoeren van de Windows Communication Foundation-voorbeelden.
Notitie
Als u Svcutil.exe gebruikt om de configuratie voor dit voorbeeld opnieuw te genereren, moet u de naam van het eindpunt in de clientconfiguratie wijzigen zodat deze overeenkomt met de clientcode.