Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W przykładzie Pooling pokazano, jak rozszerzyć program Windows Communication Foundation (WCF) na obsługę buforowania obiektów. W przykładzie pokazano, jak utworzyć atrybut, który jest składniowo i semantycznie podobny do ObjectPoolingAttribute
funkcji atrybutów usług Enterprise Services. Buforowanie obiektów może zapewnić dramatyczny wzrost wydajności aplikacji. Jednak może mieć odwrotny efekt, jeśli nie jest prawidłowo używany. Buforowanie obiektów pomaga zmniejszyć nakład pracy związany z ponownym tworzeniem często używanych obiektów, które wymagają obszernej inicjalizacji. Jeśli jednak wywołanie metody na obiekcie puli zajmuje znaczną ilość czasu, zarządzanie pulą obiektów kolejkuje dodatkowe żądania, gdy tylko zostanie osiągnięty maksymalny rozmiar puli. W związku z tym może nie obsłużyć niektórych żądań tworzenia obiektów przez zgłoszenie wyjątku przekroczenia limitu czasu.
Uwaga / Notatka
Procedura instalacji i instrukcje kompilacji dla tego przykładu znajdują się na końcu tego tematu.
Pierwszym krokiem tworzenia rozszerzenia WCF jest podjęcie decyzji o użyciu punktu rozszerzalności.
W programie WCF termin dyspozytor odnosi się do składnika uruchomieniowego, który jest odpowiedzialny za przekształcanie przychodzących komunikatów w wywołania metody na usłudze użytkownika oraz za przekształcanie wartości zwracanych z tej metody w komunikat wychodzący. Usługa WCF tworzy dyspozytor dla każdego punktu końcowego. Klient programu WCF musi używać dyspozytora, jeśli kontrakt skojarzony z tym klientem jest kontraktem dwukierunkowym.
Dyspozytorzy kanałów i punktów końcowych oferują rozszerzalność kanału i całego kontraktu, ujawniając różne właściwości kontrolujące zachowanie dyspozytora. Właściwość DispatchRuntime umożliwia również inspekcję, modyfikowanie lub dostosowywanie procesu wysyłania. Ten przykład koncentruje się na InstanceProvider właściwości wskazującej obiekt, który udostępnia wystąpienia klasy usługi.
The IInstanceProvider
W programie WCF dyspozytor tworzy wystąpienia klasy usługi przy użyciu InstanceProvider klasy, która implementuje IInstanceProvider interfejs. Ten interfejs ma trzy metody:
GetInstance(InstanceContext, Message): Po nadejściu komunikatu dyspozytor wywołuje metodę GetInstance(InstanceContext, Message) aby utworzyć wystąpienie klasy usługi w celu przetworzenia komunikatu. Częstotliwość wywołań tej metody jest określana przez właściwość InstanceContextMode. Jeśli na przykład właściwość InstanceContextMode jest ustawiona na PerCall, nowe wystąpienie klasy usługi zostanie utworzone w celu przetworzenia każdej wiadomości, więc GetInstance(InstanceContext, Message) jest wywoływana za każdym razem, gdy pojawi się wiadomość.
GetInstance(InstanceContext): Jest to identyczne z poprzednią metodą, z tą różnicą, że jest wywoływana, gdy nie ma argumentu Message.
ReleaseInstance(InstanceContext, Object): Po przekroczeniu czasu życia wystąpienia usługi, dyspozytor wywołuje metodę ReleaseInstance(InstanceContext, Object) . Podobnie jak w przypadku metody GetInstance(InstanceContext, Message), częstotliwość wywołań tej metody jest określana przez właściwość InstanceContextMode.
Pula obiektów
Implementacja niestandardowa IInstanceProvider zapewnia wymaganą semantykę zarządzania pulą obiektów dla usługi. W związku z tym ten przykład ma typ ObjectPoolingInstanceProvider
, który zapewnia niestandardową implementację IInstanceProvider do obsługi puli. Gdy Dispatcher
wywołuje metodę GetInstance(InstanceContext, Message), zamiast tworzyć nowe wystąpienie, niestandardowa implementacja szuka istniejącego obiektu w puli umieszczonej w pamięci. Jeśli jest dostępny, zostanie zwrócony. W przeciwnym razie zostanie utworzony nowy obiekt. Implementacja dla elementu GetInstance
jest pokazana w poniższym przykładowym kodzie.
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;
}
Niestandardowa implementacja ReleaseInstance
dodaje wydane wystąpienie z powrotem do puli i dekrementuje wartość ActiveObjectsCount
. Metody w Dispatcher
mogą być wywoływane z różnych wątków, dlatego wymagany jest zsynchronizowany dostęp do składowych klasy ObjectPoolingInstanceProvider
.
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();
}
}
Metoda ReleaseInstance
udostępnia funkcję "oczyszczania podczas inicjalizacji". Zwykle pula utrzymuje minimalną liczbę obiektów przez cały czas jej istnienia. Mogą jednak istnieć okresy nadmiernego użycia, które wymagają utworzenia dodatkowych obiektów w puli w celu osiągnięcia maksymalnego limitu określonego w konfiguracji. W końcu, gdy pula stanie się mniej aktywna, ten nadmiar obiektów może stać się dodatkowym obciążeniem. W związku z tym, gdy activeObjectsCount
osiągnie zero, uruchamiany jest czasomierz bezczynności, który wyzwala i wykonuje cykl czyszczenia.
Dodawanie zachowania
Rozszerzenia warstwy dyspozytora są podłączone przy użyciu następujących zachowań:
Zachowania serwisu. Umożliwiają one dostosowanie całego środowiska uruchomieniowego usługi.
Zachowania punktu końcowego. Umożliwiają one dostosowanie punktów końcowych usługi, w szczególności kanału i dyspozytora punktu końcowego.
Zachowania umowne. Umożliwiają one odpowiednio dostosowanie klas ClientRuntime i DispatchRuntime klienta i usługi.
Aby można było utworzyć rozszerzenie puli obiektów, należy utworzyć działanie usługi. Zachowania usługi są tworzone przez zaimplementowanie interfejsu IServiceBehavior . Istnieje kilka sposobów, aby model usługi był świadomy zachowań niestandardowych:
Używanie atrybutu niestandardowego.
Należy dodać ją do kolekcji zachowań opisu usługi.
Rozszerzanie pliku konfiguracji.
W tym przykładzie użyto atrybutu niestandardowego. Po skonstruowaniu ServiceHost sprawdza atrybuty używane w definicji typu usługi i dodaje dostępne zachowania do kolekcji zachowań opisu usługi.
IServiceBehavior Interfejs zawiera trzy metody — Validate, AddBindingParametersi ApplyDispatchBehavior. Metoda Validate służy do zapewnienia, że zachowanie można zastosować do usługi. W tym przykładzie implementacja gwarantuje, że usługa nie jest skonfigurowana przy użyciu polecenia Single. Metoda AddBindingParameters służy do konfigurowania powiązań usługi. Nie jest to wymagane w tym scenariuszu. ApplyDispatchBehavior jest używane do konfigurowania dyspozytorów usługi. Ta metoda jest wywoływana przez WCF podczas inicjowania ServiceHost. Następujące parametry są przekazywane do tej metody:
Description
: Ten argument zawiera opis usługi dla całej usługi. Może to służyć do sprawdzania danych opisu dotyczących punktów końcowych, kontraktów, powiązań i innych danych usługi.ServiceHostBase
: Ten argument dostarcza ServiceHostBase która jest aktualnie inicjowana.
W niestandardowej implementacji IServiceBehavior nowe wystąpienie ObjectPoolingInstanceProvider
jest tworzone i przypisywane do właściwości InstanceProvider w każdym DispatchRuntime w 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;
}
}
Oprócz implementacji IServiceBehavior, klasa ObjectPoolingAttribute ma kilku członków, aby dostosować pulę obiektów za pomocą argumentów atrybutów. Ci członkowie to MaxPoolSize, MinPoolSize i CreationTimeout, aby pasowały do funkcji buforowania obiektów udostępnianych przez .NET Enterprise Services.
Zachowanie buforowania obiektów można teraz dodać do usługi WCF, dodając adnotację do implementacji usługi przy użyciu nowo utworzonego atrybutu niestandardowego ObjectPooling
.
[ObjectPooling(MaxPoolSize=1024, MinPoolSize=10, CreationTimeout=30000)]
public class PoolService : IPoolService
{
// …
}
Uruchamianie przykładu
W przykładzie przedstawiono korzyści z wydajności, które można uzyskać przy użyciu buforowania obiektów w niektórych scenariuszach.
Aplikacja usługi implementuje dwie usługi — WorkService
i ObjectPooledWorkService
. Obie usługi współużytkują tę samą implementację — obie wymagają kosztownej inicjalizacji, a następnie uwidaczniają metodę DoWork()
, która jest stosunkowo tania. Jedyną różnicą jest to, że ObjectPooledWorkService
ma skonfigurowane buforowanie obiektów.
[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.");
}
}
Po uruchomieniu klienta program wywołuje WorkService
5 razy. Następnie wywołuje ObjectPooledWorkService
5 razy. Następnie zostanie wyświetlona różnica czasu:
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.
Uwaga / Notatka
Przy pierwszym uruchomieniu klienta obie usługi wydają się trwać mniej więcej tyle samo. Jeśli ponownie uruchomisz przykład, zobaczysz, że ObjectPooledWorkService
zwracany jest znacznie szybciej, ponieważ wystąpienie tego obiektu już istnieje w puli.
Aby skonfigurować, skompilować i uruchomić przykładowy program
Upewnij się, że wykonano procedurę instalacji One-Time dla przykładów programu Windows Communication Foundation.
Aby skompilować rozwiązanie, postępuj zgodnie z instrukcjami w temacie Building the Windows Communication Foundation Samples (Tworzenie przykładów programu Windows Communication Foundation).
Aby uruchomić przykład w konfiguracji pojedynczej lub między maszynami, postępuj zgodnie z instrukcjami w Uruchamianie przykładów programu Windows Communication Foundation.
Uwaga / Notatka
Jeśli używasz Svcutil.exe, aby ponownie wygenerować konfigurację dla tego przykładu, pamiętaj o zmianie nazwy punktu końcowego w konfiguracji klienta, aby odpowiadała ona kodowi klienta.