Buforowanie

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 w obiekcie w puli zajmuje dużo czasu, buforowanie obiektów kolejkuje dodatkowe żądania natychmiast po osiągnięciu maksymalnego rozmiaru 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

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 czasu wykonywania odpowiedzialnego za konwertowanie komunikatów przychodzących na wywołania metody w usłudze użytkownika i konwertowanie wartości zwracanych z tej metody na 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 InstanceProviderklasy , która implementuje IInstanceProvider interfejs. Ten interfejs ma trzy metody:

Pula obiektów

Implementacja niestandardowa IInstanceProvider zapewnia wymaganą semantyka buforowania obiektów dla usługi. W związku z tym ten przykład ma typ, który zapewnia niestandardową ObjectPoolingInstanceProvider implementację IInstanceProvider puli. Dispatcher Gdy metoda wywołuje metodęGetInstance(InstanceContext, Message), zamiast tworzyć nowe wystąpienie, implementacja niestandardowa wyszukuje istniejący obiekt w puli 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;
}

Implementacja niestandardowa ReleaseInstance dodaje wydane wystąpienie z powrotem do puli i dekrementuje ActiveObjectsCount wartość. Metody Dispatcher te mogą wywoływać z różnych wątków i dlatego wymagany jest zsynchronizowany dostęp do składowych na poziomie klasy w ObjectPoolingInstanceProvider klasie.

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 inicjowania". Zwykle pula utrzymuje minimalną liczbę obiektów przez okres istnienia puli. 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, te nadwyżki obiektów mogą stać się dodatkowym obciążeniem. W związku z tym activeObjectsCount po osiągnięciu zera czasomierz bezczynności jest uruchamiany, który wyzwala i wykonuje cykl czyszczenia.

Dodawanie zachowania

Rozszerzenia warstwy dyspozytora są przyłączone przy użyciu następujących zachowań:

  • Zachowania usługi. 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 kontraktu. 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ć zachowanie 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.

  • Imperatywnie dodanie jej 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. Służy ApplyDispatchBehavior do konfigurowania dyspozytorów usługi. Ta metoda jest wywoływana przez usługę 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 zawiera ServiceHostBase aktualnie inicjowane.

W implementacji niestandardowej IServiceBehavior nowe wystąpienie ObjectPoolingInstanceProvider obiektu jest tworzone i przypisywane do InstanceProvider właściwości w każdej DispatchRuntime z nich w bazie danych 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 IServiceBehavior implementacji ObjectPoolingAttribute klasa ma kilka elementów członkowskich, aby dostosować pulę obiektów przy użyciu argumentów atrybutów. Te elementy członkowskie to MaxPoolSize, MinPoolSizei CreationTimeout, aby były zgodne z zestawem funkcji buforowania obiektów udostępnianym przez usługi .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 skonfigurowano ObjectPooledWorkService 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

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ład

  1. Upewnij się, że wykonano procedurę instalacji jednorazowej dla przykładów programu Windows Communication Foundation.

  2. 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).

  3. Aby uruchomić przykład w konfiguracji pojedynczej lub między maszynami, postępuj zgodnie z instrukcjami w temacie Uruchamianie przykładów programu Windows Communication Foundation.

Uwaga

Jeśli używasz Svcutil.exe do ponownego wygenerowania konfiguracji dla tego przykładu, pamiętaj o zmodyfikowaniu nazwy punktu końcowego w konfiguracji klienta, aby był zgodny z kodem klienta.