Konkurs – vouchery na egzamin - pytania

Tak jak wczoraj pisaliśmy, dzisiaj publikujemy dwadzieścia pytań związanych z egzaminami, do których w ciągu ostatnich trzech miesięcy publikowaliśmy artykuły przygotowujące. Pytania są zamknięte i pierwsza osoba, która w komentarzu (przypominamy o poprawnym podaniu adresu email, na który wyślemy vouchery) poda poprawną odpowiedz na wszystkie pytania otrzyma nagrodę voucher na egzamin oraz voucher na kurs e-learningowy ASP.NET 3.5. Poniżej lista pytań:

70-562 – ASP:

  1. Masz DataSet zawierającego Customer DataTable oraz Order DataTable. Chcesz łatwo przechodzić z Order DataRow do Customer, który jest autorem zamówienia. Który obiekt umożliwi Ci łatwe przechodzenie z obiektów Order do obiektów Customer?

    A. DataColumn

    B. DataTable

    C. DataRow

    D. DataRelation

  2. Która z poniższych metod klasy HttpServerUtility może zostać użyta to przejścia na inną stronę aplikacji bez przesyłania o tym informacji do klienta:

    A. Redirect

    B. MapPath

    C. Transfer

    D. UrlDecode

  3. Potrzebujesz przechowywać dane, które są dostępne dla każdego użytkownika, który odwiedza Twoją stronę. Jakiej kolekcji powinieneś użyć w tym celu?

    A. Session

    B. Application

    C. Cookies

    D. ViewState

  4. Co musisz dodać do connection stringa, aby umożliwić dostęp do danych asynchroniczny?

    A. BeginExecute=true

    B. MultiThreaded=true

    C. MultipleActiveResultSets=true

    D. Asynchronous Processing=true

  5. Potrzebujesz generować dynamicznie dokumenty Worda, kiedy po aplikacji przychodzi żądanie pobrania pliku, którego rozszerzeniem jest .docx. Jak możesz to zrobić?

    A. Zaimplementować interfejs IPartitionResolver

    B. Zaimplementować interfejs IHttpModule

    C. Zaimplementować interfejs IHttpHandler

    D. Zaimplementować interfejs IHttpHandlerFactory

  6. Której klasy użyjesz podczas konwersji między typami danych .NET Framework a typami XML?

    A. XmlType

    B. XmlCast

    C. XmlConvert

    D. XmlSettings

  7. Tworzysz aplikację webową w ASP.NET w dziesiątkami stron wchodzących w jej skład. Chcesz zapisać preferencje użytkownika, tak aby można było się do tych informacji dostać z każdej strony. Dodatkowo chcesz, aby te ustawienia były zapamiętywany między kolejnymi wizytami użytkownika, nawet jak zamknie przeglądarkę. Który mechanizm zapamiętywania stanu po stronie klienta użyjesz?

    A. View state

    B. Control state

    C. Hidden fields

    D. Cookies

    E. Query strings

  8. Jaki tym uwierzytelniania na poziomie aplikacji musicie skonfigurować, aby móc skorzystać z domyślnego membership providera – AspNetSqlMembershipProvider?

    A. Windows

    B. Forms

    C. Passport

    D. None

  9. Potrzebujesz dynamicznie zmienić master page dla strony. W którym zdarzeniu strony to zrobisz?

    A. Page_Load

    B. Page_Render

    C. Page_PreRender

    D. Page_PreInit

  10. Chcesz napisać serwis WCF, który będzie hostowany przez IIS. Który typ projektu powinieneś użyć?

    A. WCF Service library

    B. WCF Service application

    C. ASP.NET Web Service application

    D. Windows Service

70-503 – WCF:

  1. Mamy klasę: 
    [ServiceBehavior()]
    public class ServiceImplementation : IServiceInterface
    {
        private int hitCounter;
        public void Increment()
        {
            hitCounter++;
        }
    }

    Jak należy udekorować klasę ServiceImplementation aby problemy współbieżności zostały wyeliminowane bez dodawania kodu w klasie? (Wybierz wszystkie poprawne odpowiedzi)

    A. ConcurrencyMode=Multiple i InstanceContextMode=Single

    B. ConcurrencyMode=Single i InstanceContextMode=PerSession

    C. ConcurrencyMode=Multiple i InstanceContextMode=PerSession

    D. ConcurrencyMode=Single i InstanceContextMode=Single

  2. Który z poniższych punktów rozszerzenia (po stronie serwisu) musi być też zaimplementowany po stronie klienta?

    A. Message Inspection

    B. Message Formatting

    C. Parameter Inspection

    D. Operation Invoker

  3. W obiekcie wiadomości właściwość MessageState jest ustawiona na Written. Które z poniższych zdarzeń zaszło?

    A. Metoda GetReaderAtBodyContents została wywołana.

    B. Metoda WriteBodyContents została wywołana.

    C. Metoda CreateBufferedCopy została wywołana.

    D. Metoda CreateMessage została wywołana.

  4. Właśnie definiujesz nowy kontrakt serwisu. Które z poniższych reprezentują atrybuty z przestrzeni System.ServiceModel, które na pewno będą potrzebne?

    A. ServiceContractAttribute i FaultContractAttribute

    B. OperationContractAttribute i FaultContractAttribute

    C. ServiceContractAttribute i OperationContractAttribute

    D. OperationContractAttribute i MessageParameterAttribute

  5. Będziesz obsługiwać serwis napisany w javie. Które z poniższych są poprawnymi metodami utworzenia proxy WCF do obsługi serwisu? (Wybierz wszystkie poprawne)

    A. Użyj klasy ChannelFactory class do utworzenia obiektu proxy dynamicznie.

    B. Użyj polecenia svcutil do utworzenia obieku proxy na podstawie definicji WSDL

    C. Ręcznie utwórz klasę proxy dziedziczącą po ClientBase.

    D. Dodaj referencję serwisu w Visual Studio (dodając referencję WSDL serwisu do projektu).

  6. Potrzebujesz uruchomić serwis na serwerze IIS. Która wersja IIS obsługuje protokoły nie-HTTP?

    A. IIS 5.1

    B. IIS 6.0

    C. IIS 7.0

    D. Żadna z wersji IIS

  7. Utworzyłeś aplikację kliencką WCF, która musi obsługiwać wywołanie zwrotne z serwisu. Który z bindingów użyjesz aby spełnić to wymaganie?

    A. basicHttpBinding

    B. wsHttpBinding

    C. wsHttpContextBinding

    D. wsDualHttpBinding

  8. Utworzyłeś aplikację kliencką WCF, która musi obsługiwać wywołanie zwrotne z serwisu. Który z poniższych bindingów NIE wspiera tego wymagania?

    A. netTcpBinding

    B. netNamedPipeBinding

    C. netMsmqBinding

    D. netTcpContextBinding

  9. Jaki będzie rezultat poniższego kodu?
    using (TransactionScope ts1 = new TransactionScope(
        TransactionScopeOption.Required, 
        new Timespan(0, 0, 30)))
    {
        using (TransactionScope ts2 = new TransactionScope(
            TransactionScopeOption.Required, 
            new Timespan(0, 0, 40)))
        {
            // Update database
            // Sleep for 35 seconds
            ts2.Complete();
        }
        ts1.Complete();
    }

    A. Obie transakcje zostaną zatwierdzone.

    B. Obie transakcje zostaną wycofane.

    C. Transakcja ts2 zostanie zatwierdzona a ts1 wycofana.

    D. Transakcja ts1 zostanie zatwierdzona, a ts2 wycofana.

  10. Masz aplikację Windows Forms. W pojedynczej transakcji Twoja aplikacja musi zaktualizować dwie bazy Microsoft SQL Server. Który koordynator transakcji zarządza tymi transakcjami?

    A. Lightweight Transaction Manager

    B. Microsoft Distributed Transaction Controller

    C. Kernel Transaction Manager

    D. Web Services Transaction Manager

Powodzenia!

Tagi: , , , , , ,

Konkurs – vouchery na egzamin

Tak jak zapowiadaliśmy wcześniej, mamy dla Was niespodziankę!

Mamy do rozdania kilka nagród:

  • 2 vouchery na dowolny egzamin (ważne do 30 lipca 2010)
  • 2 vouchery na kurs e-learningowy 6364: Visual Studio 2008 ASP.Net 3.5 (ważne do 30 czerwca 2010)

Chcieliśmy podziękować Olsztyńskiej Grupie IT za vouchery. Dzięki!!!

Konkurs na najlepszą “zajawkę”

Waszym zadaniem jest napisanie notki z linkami (70-536, 70-562, 70-503) do naszych kursów w jakimś blogu/serwisie. Najlepsza “zajawka” będzie nagrodzona: voucherem na egzamin, a kolejna voucherem na kurs e-learningowy.

Termin zgłaszania “zajawek” (termin wpisania linku do nich w komentarzach) godz. 12.00, poniedziałek 14 czerwca 2010 r!

Konkurs na najszybszego czytelnika

Jutro (piątek 11 czerwca 2010) o godzinie 12.00 pojawią się 20 pytania z zakresu przedstawianych przez nas kursów (dziesięć z WCF, dziesięć z ASP.NET).

Najszybsza osoba, która wpisze w komentarzu poprawne odpowiedzi na wszystkie pytania otrzyma voucher na egzamin i voucher na kurs e-learningowy.

Skrócony regulamin:
  • nagrody nie podlegają wymianie na ich wartość pieniężną
  • wszelkie konflikty rozwiązuje oraz “zajawki” ocenia komisja w składzie: Daniel Plawgo, Dawid Cieszyński, Kamil Lemański, Daniel Jarzynka, Dawid Tulski
  • członkowie komisji nie mogą brać udziału w konkursach
  • do komunikacji z zwycięzcami będzie użyty adres email pozostawiony w komentarzu

Tagi: , , , , , ,

70-503: Synchronization

Ten artykuł pochodzi z serii przygotowań do egzaminu 70-503: Windows Communication Foundation.

Ci z Was, którzy obsługiwali już wątki w .NET wiedzą, że nie jest to specjalnie skomplikowane. Najczęściej problemy występują przy obsłudze kontrolek Windows Forms, ponieważ ich właściwości mogą być zmieniane tylko w wątku, który je stworzył. Innym problemem jest wykorzystywanie lokalnej pamięci wątków do przechowywania informacji o kontekście, gdy proces nieoczekiwanie zmienia wątki, te dane mogą zniknąć. Z tej lekcji dowiemy się jak powyższe problemy są obsługiwane w WCFie.

Kontekst synchronizacji

W .NET 2.0 została wprowadzona rzadko używana funkcjonalność zwana kontekstem synchronizacji (klasa SynchronizationContext). Umożliwia ona sprawdzenie czy aktualnie wykonywany kod znajduje się w odpowiednim wątku. Aktualny kontekst możemy otrzymać odwołując się do statycznego pola SynchronizationContext.Current. Gdy jakaś metoda ma być wywołana w wątku, który nie jest bieżącym wątkiem, wywołujący wątek tworzy delegat typu SendOrPostCallback odwołujący się do żądanej metody a następnie jest przekazywany do metody Post (wywołanie asynchroniczne) lub Send (wywołanie synchroniczne) obiektu SynchronizationContext.

WCF i synchronizacja

A co ma kontekst synchronizacji do WCF’a? Nie wiem czy wiecie, że jeśli nie jest ustawione inaczej, każde wywołanie metod obiektu serwisu jest wykonywane przez wątki wejścia/wyjścia, z których żaden nie należy do naszej aplikacji. Gdybyśmy chcieli teraz zaktualizować coś w interfejsie użytkownika to napotkamy problem.

Z pomocą przychodzi nam właściwość UseSynchronizationContext klasy ServiceBehavior:

[ServiceBehavior(UseSynchronizationContext=true)]
public class UpdateService : IUpdateService

Ustawienie tego atrybutu na true spowoduje, że WCF będzie sprawdzał wątek uruchamiający hosta i jeśli wątek ten posiada kontekst synchronizacji i metoda serwisu jest wywołana z innego wątku, to będzie ona przekazywana do właściwego wątku i przez niego uruchomiona.

W przypadku hostowania serwisu w aplikacji Windows Forms/WPF, jeśli najpierw zostanie utworzony host przed oknem, nie zostanie utworzony żaden kontekst synchronizacji i każda aktualizacja kontrolek spowoduje błąd. Najpierw należy utworzyć okno a potem dopiero hosta serwisu.

Własny kontekst synchronizacji

WCF udostępnia tylko jedną klasę umożliwiającą obsługę kontekstu synchronizacji, dzięki której możemy np. aktualizować kontrolki w oknie aplikacji hostującej serwis. Możemy także utworzyć własne konteksty synchronizacji. Klasa kontekstu synchronizacjo odpowiada za wykonywanie metod serwisu przez konkretne wątki. Możemy wykorzystać ją np. do priorytetowania obsługi. Nasz kontekst synchronizacji może przekazywać wykonanie ważniejszych metod do wątków o wyższym priorytecie a pozostałych do innych wątków. Możemy właściwie zrobić o wiele więcej bazując na tym co udostępniają nam wątki.

Na początek potrzebna jest klasa bazująca na SynchronizationContext.

   1: public class ThreadPoolSynchronizer : SynchronizationContext, IDisposable
   2: {
   3:     Queue<WorkItem> workItemQueue;
   4:     WorkerThread[] workerThreads;
   5:     Semaphore itemAdded;
   6:  
   7:     public ThreadPoolSynchronizer(int poolSize)
   8:     {
   9:         if (poolSize <= 0)
  10:             throw new InvalidOperationException("Pool size cannot be zero");
  11:  
  12:         workItemQueue = new Queue<WorkItem>();
  13:  
  14:         workerThreads = new WorkerThread[poolSize];
  15:         for (int index = 0; index < poolSize; index++)
  16:             workerThreads[index] = new WorkerThread(index + 1, this);
  17:     }
  18:  
  19:     public void Close()
  20:     {
  21:         foreach (WorkerThread thread in workerThreads)
  22:             thread.Abort();
  23:     }
  24:  
  25:     public void Abort()
  26:     {
  27:         foreach (WorkerThread thread in workerThreads)
  28:             thread.Abort();
  29:     }
  30:  
  31:     public void Dispose()
  32:     {
  33:         this.Close();
  34:     }
  35:  
  36:     public override void Post(SendOrPostCallback method, Object state)
  37:     {
  38:         WorkItem workItem = new WorkItem(method, state);
  39:         QueueWorkItem(workItem);
  40:     }
  41:  
  42:     public override void Send(SendOrPostCallback method, Object state)
  43:     {
  44:         if (SynchronizationContext.Current == this)
  45:         {
  46:             method(state);
  47:             return;
  48:         }
  49:         WorkItem workItem = new WorkItem(method, state);
  50:         QueueWorkItem(workItem);
  51:         workItem.AsyncWaitHandle.WaitOne();
  52:     }
  53:  
  54:     protected Semaphore ItemAdded
  55:     {
  56:         get
  57:         {
  58:             if (itemAdded == null)
  59:                 itemAdded = new Semaphore(0, Int32.MaxValue);
  60:  
  61:             return itemAdded;
  62:         }
  63:         set
  64:         {
  65:             itemAdded = value;
  66:         }
  67:     }
  68:  
  69:     virtual internal void QueueWorkItem(WorkItem workItem)
  70:     {
  71:         lock (workItemQueue)
  72:         {
  73:             workItemQueue.Enqueue(workItem);
  74:             ItemAdded.Release();
  75:         }
  76:     }
  77:  
  78:     protected virtual bool QueueEmpty
  79:     {
  80:         get
  81:         {
  82:             lock (workItemQueue)
  83:             {
  84:                 if (workItemQueue.Count > 0)
  85:                 {
  86:                     return false;
  87:                 }
  88:                 return true;
  89:             }
  90:         }
  91:     }
  92:     internal virtual WorkItem GetNext()
  93:     {
  94:         ItemAdded.WaitOne(1000);
  95:         lock (workItemQueue)
  96:         {
  97:             if (workItemQueue.Count == 0)
  98:             {
  99:                 return null;
 100:             }
 101:             return workItemQueue.Dequeue();
 102:         }
 103:     }
 104:  
 105: }

Mamy tutaj prostą implementację własnej obsługi wątków. Dodatkowo utworzone są klasy WorkItem i WorkerThread (nazwy mówią same za siebie, poniżej pokażę ich kod).

Za funkcjonalność synchronizacji odpowiada pięć metod podzielonych na dwie grupy. Trzy metody operacyjne (Close, Abort, Dispose – linie 19-34) odpowiadają za zatrzymywanie wątku obsługującego żądanie. Dwie metody funkcyjne (Post i Send – linie 36-52) używane są przez WCF do uruchomienia metody. W metodzie Send (linie 44-48) sprawdzamy czy aktualny kontekst nie jest naszym kontekstem aby nie spowodować zakleszczenia.

Dla pełności przedstawiam jeszcze klasy WorkItem i WorkerThread:

   1:   [Serializable]
   2:   internal class WorkItem
   3:   {
   4:       object state;
   5:       SendOrPostCallback method;
   6:       ManualResetEvent asyncWaitHandle;
   7:  
   8:       public WaitHandle AsyncWaitHandle
   9:       {
  10:           get
  11:           {
  12:               return asyncWaitHandle;
  13:           }
  14:       }
  15:  
  16:       internal WorkItem(SendOrPostCallback method, object state)
  17:       {
  18:           this.method = method;
  19:           this.state = state;
  20:           asyncWaitHandle = new ManualResetEvent(false);
  21:       }
  22:  
  23:       internal void CallBack()
  24:       {
  25:           method(state);
  26:           asyncWaitHandle.Set();
  27:       }
  28:   }

 

 

 

   1: internal class WorkerThread
   2: {
   3:     ThreadPoolSynchronizer context;
   4:     public Thread threadObj;
   5:     bool endLoop;
   6:  
   7:     public int ManagedThreadId
   8:     {
   9:         get
  10:         {
  11:             return threadObj.ManagedThreadId;
  12:         }
  13:     }
  14:  
  15:     internal WorkerThread(int threadNumber, ThreadPoolSynchronizer context)
  16:     {
  17:         this.context = context;
  18:  
  19:         endLoop = false;
  20:         threadObj = null;
  21:  
  22:         threadObj = new Thread(Run);
  23:         threadObj.IsBackground = true;
  24:         threadObj.Name = "Tread-" + threadNumber.ToString();
  25:         threadObj.Start();
  26:     }
  27:  
  28:     bool EndLoop
  29:     {
  30:         set
  31:         {
  32:             lock (this)
  33:             {
  34:                 endLoop = value;
  35:             }
  36:         }
  37:         get
  38:         {
  39:             lock (this)
  40:             {
  41:                 return endLoop;
  42:             }
  43:         }
  44:     }
  45:  
  46:     void Start()
  47:     {
  48:         Debug.Assert(threadObj != null);
  49:         Debug.Assert(threadObj.IsAlive == false);
  50:         threadObj.Start();
  51:     }
  52:  
  53:     void Run()
  54:     {
  55:         Debug.Assert(SynchronizationContext.Current == null);
  56:         SynchronizationContext.SetSynchronizationContext(context);
  57:  
  58:         while (EndLoop == false)
  59:         {
  60:             WorkItem workItem = context.GetNext();
  61:             if (workItem != null)
  62:             {
  63:                 workItem.CallBack();
  64:             }
  65:         }
  66:     }
  67:  
  68:     public void Abort()
  69:     {
  70:         Debug.Assert(threadObj != null);
  71:         if (threadObj.IsAlive == false)
  72:         {
  73:             return;
  74:         }
  75:         EndLoop = true;
  76:  
  77:         threadObj.Join();
  78:     }
  79: }
  80:  

 

Teraz musimy przypisać nowy kontekst synchronizacji, może to wyglądać tak (przypisanie w linii 2):

   1: ThreadPoolSynchronizer syncContext = new ThreadPoolSynchronizer(3);
   2: SynchronizationContext.SetSynchronizationContext(syncContext);
   3: try
   4: {
   5:     ServiceHost host = new ServiceHost(typeof(UpdateService));
   6:     host.Open();
   7:     // Block until ready to quit
   8:     host.Close();
   9: }
  10: finally
  11: {
  12:     syncContext.Dispose();
  13: }

Innym lepszym sposobem jest udekorowanie klasy serwisu własnym atrybutem (tworzenie atrybutów wykracza poza ramy tego kursu). Może to wyglądać np. tak (atrybut z dwoma parametrami rozmiar puli oraz nazwa klasy serwisu):

[ThreadPoolSynchronization(3, typeof(UpdateService))]
[ServiceBehavior(typeof(IUpdateService))]
public class UpdateService : IUpdateService

Atrybut ten musi implementować interfejs IContractBehavior a w nim następujące metody:

  • AddBindingParameters – modyfikuje bindingi,
  • ApplyClientBehavior – modyfikuje lub rozszerza zachowanie serwisu dla wybranych lub wszystkich wiadomości,
  • ApplyDispatchBehavior - “wersja od strony serwisu” metody ApplyClientBehavior – rozszerza zachowanie dla przychodzących wiadomości,
  • Validate – potwierdza że kontrakt i punkt końcowy mogą obsłużyć zachowanie zaimplementowane w obiekcie.

Ponieważ chcemy zmienić  zachowanie po stronie serwisu, interesować nas będzie metoda AppliDispatchBehavior, która może być zaimplementowana np tak:

void ApplyDispatchBehavior(ContractDescription
description, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
    // ...
    if (dispatchRuntime.SynchronizationContext == null)
        dispatchRuntime.SynchronizationContext = new ThreadPoolSynchronizer(3);
    // ...
}

Synchronizacja i wywołania zwrotne

Obsługując wywołania zwrotne (ang. callback) także musimy brać pod uwagę problemy z współbieżnością. Podobnie jak w przypadku serwisu, u klienta możemy ustawić odpowiednie tryby synchronizacji, zarówno imperatywnie jak i deklaratywnie:

[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Single)]
class CallbackClient : ICallback
{
    // Implementation code
}

Tak jak w ConcurrencyMode w klasie ServiceBehavior tak i tutaj mamy trzy możliwe wartości:

  • Single – tylko jedno wywołanie zwrotne jest możliwe w danym czasie, to gwarantuje nam że WCF nie wywoła metody więcej niż raz w tym samym czasie, nie gwarantuje natomiast że inne wątki klienta będą się odwoływać do zasobów używanych w tej metodzie, o to musimy sie już sami martwić;
  •  Multiple – dozwolone jest wielokrotne wywołanie metody, musimy sami postarać się o obsługę dostępu wielowątkowego;
  • Reentrant – metoda może być wywoływana ponownie przez serwis w tym samym wątku.

Wywołania zwrotne i konteksty synchronizacji

Możliwe jest także korzystanie z kontekstów synchronizacji w wywołaniach zwrotnych, wystarczy odpowiednio oznaczyć metodę:

[CallbackBehavior(UseSynchronizationContext=true)]
public class CallbackClient : ICallback
{
    // Implementation code
}

Na koniec jeszcze ważna informacja na temat wątków, wywołań zwrotnych i zakleszczeń. Załóżmy, że mamy przycisk uruchamiający metodę serwisu. Serwis dokonuje wywołania zwrotnego do aplikacji (dokładniej do wątku interfejsu użytkownika bo stamtąd pochodziło wywołanie serwisu). Wątek interfejsu użytkownika jest teraz zajęty bo czeka na odpowiedź od serwisu… No i mamy zakleszczenie. Jedynym rozwiązaniem tego problemu jest zrezygnowanie z kontekstu synchronizacji i ustawienie UseSynchronizationContext na false.

Na tym kończymy kurs.

Pozostańcie jeszcze z nami gdyż przygotowaliśmy małą niespodziankę.

Do zobaczenia na egzaminach :)

Tagi: , , , ,

70-503: Concurrency in WCF Applications

Ten artykuł pochodzi z serii przygotowań do egzaminu 70-503: Windows Communication Foundation.

Współbieżność (ang. concurrency) w serwisie WCF występuje, kiedy jednocześnie więcej niż jedno wywołanie ma miejsce. Celem serwisu WCF jest przetwarzanie przychodzących żądań. Kiedy żądanie przychodzi do serwisu, serwis rozdziela (ang. dispatch) komunikaty na własne wątki, które brane są z puli wątków. Z każdym żądaniem powiązany jest obiekt serwisu – instancja klasy, która implementuje interfejs serwisu. W WCF kwestia współbieżności zależy od tego, jak te obiekty są tworzone i dzielone pomiędzy pojedyncze żądania.

WCF przewiduje trzy możliwe tryby dzielenia obiektu serwisu:

  • Single – każdy wątek, który obsługuje żądanie może mieć dostęp do obiektu serwisu, ale tylko jeden w danym czasie, używanie trybu Single zmniejsza ilość problemów związanych z współbieżnością,
  • Reentrant – tylko jeden wątek może mieć dostęp do obiektu serwisu w danym czasie, jednak ma on możliwość opuszczenia obiektu i powrotu do niego w późniejszym czasie,
  • Multiple – obiekt serwisu obsługuje jednocześnie wiele żądań; jest to najtrudniejszy tryb do implementacji, ponieważ wymaga wielkiej staranności przy korzystaniu z zasobów dzielonych (ang. shared resources).

Tryb współbieżności jest ustawiany korzystając z atrybutu ServiceBehavior - ConcurrenczMode, na klasie, która implementuje serwis.

Tryb współbieżności Single

Ustawienie ConcurrencyMode na wartość ConcurrencyMode.Single gwarantuje najbardziej bezpieczne środowisko dla współbieżności.

   1: [ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Single)]
   2: public class ServiceImplementation : IServiceInterface
   3: {
   4:   // TODO: Implementacja...
   5: }

Przed rozpoczęciem przetwarzania żądania, na obiekt serwisu zakładana jest blokada, która zdejmowana jest dopiero po zakończeniu operacji. Jeżeli w tym czasie przyjdą kolejne żądania, zostaną one odłożone na kolejkę (FIFO – first-in, first-out) i czekają, kiedy obiekt serwisu będzie dostępny. Przetwarzanie pojedynczego żądania w danym czasie eliminuje problemy zarządzanie współbieżnością. Jedyną sytuacją, kiedy może pojawić się problem jest sytuacja, gdy obiekt serwisu wykonuje operacje wielowątkowe. Istnieje tu jednak pewien kompromis, którym zależy od trybu wystąpienia. Innymi słowy należy uwzględnić związek pomiędzy ConcurrencyMode a InstanceContextMode.

Przykład: Zakładamy, że mamy podaną gotową implementację serwisu. Jak możemy udekorować klasę, aby wyeliminować współbieżność, nie modyfikując niczego wewnątrz klasy?

   1: [ServiceBehavior()]
   2: public class ServiceImplementation : IServiceInterface
   3: {
   4:   private static int hitCounter;
   5:   public void Increment()
   6:   {
   7:     hitCounter++;
   8:   }
   9: }

Możemy ustawić ConcurrencyMode na Single a InstanceContextMode na PerSession lub Single. Wykorzystanie trybu współbieżności Single zapewnia, że tylko jedno żądanie może być przetwarzane w danej chwili. Połączenie ConcurrencyMode w trybie Single i InstanceContextMode w trybie Single daje gwarancję, że jest tworzona tylko jedna instancja serwisu. To połączenie jest wymagane w przypadku operacji ze statycznymi lub dzielonymi zmiennymi, aby były zabezpieczone na naruszenia współbieżności.

Tryb Single cechuje potencjalnie niska przepustowość spowodowana przesyłaniem przychodzących żądań przez jeden obiekt.

Tryb współbieżności Multiple

W przypadku serwisów, które wymagają większej przepustowości dostępny jest model wielowątkowy. Kiedy ConcurrencyMode jest ustawiony na Multiple, nie występuje już zakładanie blokady na obiekt serwisu przed obsłużeniem żądania, a obiekt serwisu może (w zależności od trybu wystąpienia) obsłużyć wiele żądań jednocześnie. Informacja o stanie serwisu i dzielonych zasobach musi być chroniona poprzez wykorzystanie standardowych technik synchronizacji, które oferuje .NET framework.

Tryb współbieżności Reentrant

Serwis po ustawienie trybu na Reentrant zachowuje się tak samo jak w przypadku trybu Single. Przed przetworzeniem żądania zakładana jest blokada na serwis i trzymana jest tak długo, jak długo trwają operacje. Różnica leży w tym, co może się wydarzyć podczas samego przetwarzania.

imagePodczas przetwarzania żądania przez serwis może wystąpić sytuacja, że serwis musi wykonać operację na innym serwisie. Inne żądania do serwisu muszą czekać na zakończenie aktualnie przetwarzanego przez serwis żądania, gdzie serwis czeka na zakończenie wywołania, które sam rozpoczął. Problem ten ilustruje zamieszczony rysunek.

Co się teraz stanie, jeżeli zewnętrzny serwis wywoła żądanie na naszym serwisie WCF? Żądanie zostanie dodane do kolejki, obiekt serwisu nie zostanie odblokowany, wystąpi zakleszczenie! (WCF jest w stanie poradzić sobie z tym przez unieważnienie żądanie po pewnym czasie – timeout, lub rzucenie wyjątku InvalidOperationException). Tryb Reentrant rozwiązuje ten problem.

Różnica między trybem Single a Reentrant jest taka, że w tym drugim trybie, kiedy obiekt serwisu wykonuje własne żądanie zdejmowana jest blokada. Pozwala to na obsłużenie innych żądań. Kiedy odpowiedz na żądanie serwisu nadejdzie, zostanie dodana do kolejki razem z innymi żądaniami. Kiedy zacznie się jego przetwarzanie, założy blokadę na obiekt serwisu i dokończy swoje zadanie.

Chociaż konfiguracja serwisu do działania w trybie reentrant jest prosta, programista musi musi liczyć się z dużą odpowiedzialnością wiążącą się z tym rozwiązaniem. Zakleszczenia są problemem, jednak nie jedynym. Programista musi zapewnić, że kiedy serwis wykonuje żądanie, stan serwisu musi pozostać spójny – serwis nie powinien oddziaływać ze swoimi własnymi polami (publicznymi, czy prywatnymi, należącymi do instancji, czy statycznymi) w taki sposób, aby jakikolwiek obiekt pozostał w nieakceptowanym stanie. Przykładowo, jeżeli budujemy strukturę drzewiastą, nie możemy dopuścić do tego, aby korzeń drzewa nie był zdefiniowany.

W następnej, ostatniej już lekcji, zapoznamy się z synchronizacją.

Tagi: , , , , ,

70-503: Programming Transactions

Ten artykuł pochodzi z serii przygotowań do egzaminu 70-503: Windows Communication Foundation.

No to wiemy już jak włączyć transakcje i co trzeba zrobić, zarówno po stronie serwisu jak i po stronie klienta, aby informacje o transakcji były przekazywane w obie strony. Dzisiaj dowiemy się więcej o obsłudze transakcji od strony kodu.

Transakcje otoczenia

W .NET Framework 2.0 w przestrzeni nazw System.Transaction zostały wprowadzone tzw. transakcje otoczenia (ang. Ambient Transactions). Polega to na tym, że transakcja istnieje w aktualnym wątku lub w kontekście obiektu i wszystkie działania w ramach tego wątku lub kontekstu wchodzą w skład tej transakcji (o ile taka transakcja została w ogóle rozpoczęta).

Aby sprawdzić czy mamy dostępną transakcję otoczenia sprawdzamy statyczne pole Current klasy Transaction:

Transaction ambientTransaction = Transaction.Current;

Jeśli nie ma transakcji, Transaction.Current jest równe null.

Jeśli nasz kod zostanie wywołany poprzez zdalnego klienta (przez binding w którym jest włączone przekazywanie transakcji) to ta lokalna transakcja stanie się transakcją rozproszoną (ang. distributed transaction).

Klasa Transaction (i obiekt Transaction.Current) udostępnia właściwość TransactionInformation, z której możemy dowiedzieć się jaka jest “lokalność” transakcji. Właściwość TransactionInformation zawiera dwa pola: LocalIdentifier – string zawierający unikalny identyfikator aktualnej transakcji, oraz DistributedIdentifier – string zawierający globalny unikalny identyfikator transakcji rozproszonej (GUID). Jeśli transakcja nie została podniesiona do rangi rozproszonej transakcji, wartość DistributedIdentifier będzie równa Guid.Empty.

Klasa TransactionScope

Jak sama nazwa wskazuje, klasa TransactionScope definiuje przestrzeń transakcji:

using (TransactionScope ts = new TransactionScope())
{
    //
    // tutaj aktualizujemy dane w systemie, itp.
    //
 
    ts.Complete();
}

W zależności od tego czy nasz kod jest wykonywany w ramach transakcji otoczenia (ang. ambient transaction) to, albo jest tworzona nowa transakcja LTM (jeśli nie ma transakcji otoczenia), albo to co robimy w ramach tej przestrzeni transakcji jest podłączane pod aktywną transakcję otoczenia.

Istotny jest sposób w jaki zakańczana jest transakcja. Transakcja istnieje dopóki istnieje obiekt klasy TransactionScope (dlatego jest ważne jest użycie using, lub jawne zwolnienie obiektu). Jeśli nie zwolnimy sami obiektu transakcji, może on nie być w ogóle zwolniony (czyli będzie aktywny przez całe działanie serwisu – o tym decyduje odśmiecacz (ang. garbage collector)), a nasza transakcja zakończy się niepowodzeniem przez przekroczenie czasu (ang. timeout).

Jeśli przed zwolnieniem obiektu klasy TransactionScope nie zostanie wywołana metoda Complete() całość transakcji zostanie wycofana (ang. rollback). Jeśli metoda Complete() zostanie wywołana, przy zwalnianiu obiektu cała transakcja zostanie zatwierdzona (ang. commit).

Głosowanie w transakcjach

Tak jak zostało powiedziane w poprzedniej lekcji, transakcje LTP charakteryzują się dwufazowym procesem zatwierdzania transakcji. Wywołanie metody Complete() obiektu TransactionScope nie gwarantuje zatwierdzenia transakcji, informuje ono tylko menadżera transakcji o tym, że u nas jest wszystko w porządku.

W przypadku gdy inny uczestnik transakcji zgłosi niepowodzenie, przy wywołaniu u nas metody Complete() dostaniemy wyjątek TransactionAbortedException. Tak więc obsługę transakcji musimy uzupełnić o obsługę tego wyjątku:

try
{
    using(TransactionScope ts = new TransactionScope( ))
    {
        /* Perform updates here */
        ts.Complete( );
    }
}
catch(TransactionAbortedException e)
{
    /* Rollback updates, if necessary */
}

Zagnieżdżanie transakcji

Domyślnie utworzenie nowej transakcji spowoduje podpięcie się pod aktualną transakcję, ewentualnie jeśli takiej nie ma to  utworzenie nowej. Jeden z konstruktorów klasy TransactionScope przyjmuje parametr typu TransactionScopeOption, który pozwala nam decydować czy chcemy utworzyć transakcję zagnieżdżoną czy dołączyć się do bieżącej transakcji, Wartości TransactionScopeOption:

  • Required – użyta jest transakcja otoczenia, lub stworzona nowa transakcja jeśli transakcja otoczenia nie istnieje, to jest wartość domyślna (tworząc obiekt klasy TransactionScope chcemy fragment kodu objąć transakcją, bez względu na to czy transakcja już istnieje czy nie),
  • RequiresNew – zawsze jest tworzona nowa transakcja i jest ona jednocześnie transakcją główną (ang. root transaction) dla wszystkich następnych,
  • Suppress – nawet jeśli istnieje transakcja otoczenia, żadne zmiany w tym fragmencie kodu nie zostaną uwzględnione w transakcji, ta opcja służy do wykonania kodu poza transakcją, wszelkie próby kontaktu z kodem działającym w transakcjach (np. wywołanie metody serwisu, która działa w transakcji spowoduje błąd)

Przykład zagnieżdżonych transakcji:

using(TransactionScope ts1 = new TransactionScope())
{
    using(TransactionScope ts2 = new TransactionScope())
    {
        ts2.Complete();
    }
    ts1.Complete();
}

Aby obie transakcje były zatwierdzone, metoda Complete() musi być wykonana dwukrotnie (raz dla każdego obiektu). Gdyby transakcja ts1 nie została zatwierdzona, żadne zmiany (nawet te zatwierdzone przez ts2) nie zostaną wprowadzone:

using(TransactionScope ts1 = new TransactionScope())
{
    using(TransactionScope ts2 = new TransactionScope())
    {
        ts2.Complete();
    }
}

Kolejnym zagadnieniem jest izolacja transakcji. Niektóre konstruktory klasy TransactionScope przyjmują jako parametr strukturę TransactionOptions, w której jedną z właściwości jest IsolationLevel:

  • Serializable – najwyższy poziom, inni użytkownicy nie widzą danych modyfikowanych w ramach transakcji, inne procesy nie mogę zmienić lub dodać danych będących w konflikcie z tą transakcją,
  • RepeatableRead – inne procesy mogą oglądać dane modyfikowane przez tą transakcję, ale te dane nie mogą być modyfikowane, ale mogą być dodane nowe dane które będą widoczne w transakcji,
  • ReadCommitted – inne procesy nie mogą oglądać niezatwierdzonych (ang. uncommited) danych,
  • ReadUncommitted – inne procesy mogą odczytywać i modyfikować niezatwierdzone dane,
  • Snapshot -  dane mogą być odczytywane, przed zatwierdzeniem transakcji sprawdzane jest czy w trakcje transakcji nie zostały zmienione dane (np. przez procesy zewnętrzne) poprzednio wczytane i modyfikowane w ramach transakcji
  • Chaos – zmiany z bardziej izolowanych transakcji nie mogą być zastępowane,
  • Unspecified – używany jest inny poziom izolacji, którego nie można określić, ustawienie takiej wartości spowoduje wyrzucenie wyjątku

Zagnieżdżone transakcje muszą używać tego samego poziomu izolacji jeśli chcą dołączyć do transakcji otoczenia (ang. ambient transaction), w przeciwnym przypadku wystąpi wyjątek ArgumentException.

Limity czasu transakcji

Możemy ustawić maksymalny czas trwania transakcji. Poniżej konstruktor z ustawionym czasem:

TransactionScope ts = new TransactionScope(TransactionScopeOption.Required,
    new TimeSpan(0, 10, 0));

Możemy też ustawić nieskończony czas trwania (co oczywiście jest dość ryzykowne):

TransactionScope ts = new TransactionScope(TransactionScopeOption.Required,
    TimeSpan.Zero);

Popatrzmy jeszcze na przykład zagnieżdżonych transakcji:

   1: using(TransactionScope ts1 = new
   2:     TransactionScope(TransactionScopeOption.Required,
   3:         new TimeSpan(0, 2, 0) ))
   4: {
   5:     // A transaction with a timespan of 2 minutes is created
   6:     using(TransactionScope ts2 = new
   7:         TransactionScope(TransactionScopeOption.Required,
   8:             new TimeSpan(0, 1, 0) ))
   9:     {
  10:         // A transaction with a timespan of 1 minute is created
  11:         Thread.Sleep(90000);
  12:     }
  13: }

Tutaj transakcja ts1 ma timeout 2 minuty (linia 3), a transakcja ts2: 1 minuta (linia 8). W transakcji ts2 występuje operacja (linia 11) trwająca dłużej niż 2 minuty i to transakcja ts2 spowoduje wycofanie całego bloku (włącznie z ts1).

Na koniec jeszcze informacja jak ustawić domyślny timeout:

<system.transactions>
    <defaultSettings timeout="00:00:10" />
</system.transactions>

W ostatnich lekcjach tego kursu powiemy odrobinę o współbieżności.

Tagi: , , , ,

70-503: Transaction Basics

Ten artykuł pochodzi z serii przygotowań do egzaminu 70-503: Windows Communication Foundation.

Podstawową funkcją transakcji jest zagwarantowanie zasad ACID:

  • atomowości (ang. atomicity),
  • spójności (ang. consistency),
  • izolacji (ang. isolation),
  • trwałości (ang. durability).

Kiedy operacje związane z bazą odbywają się na wielu maszynach i wielu zbiorach danych, nie jest to takie proste. WCF wspomaga programistę w tym zadaniu.

W celu spełnienia zasad ACID najczęstszym podejściem jest wykorzystanie dwuetapowego zgłoszenia (ang. two-phase commit):

  1. Etap przygotowania (ang. prepare phase) – koordynator transakcji zarządza tym etapem, wysyła żądanie przygotowania do wszystkich zarządców transakcji (ang. transaction manager), na maszynach biorących udział w procesie. Zarządcy odsyłają informację o tym, czy operacje zakończyły się sukcesem, czy porażką. Kiedy wszyscy odpowiedzą etap przygotowania uznajemy za zakończony.
  2. Etap zgłoszenia (ang. commit phase) – zależny od wyników etapu poprzedniego; jeżeli tamten zakończy się sukcesem – wysyłane zostaje żądanie Commit, w przeciwnym wypadku, jeżeli chociaż jedna maszyna zwróci komunikat błędu – koordynator transakcji wysyła żądanie Abort, aby poinformować zarządców o potrzebie cofnięcia zmian.

W zależności od sytuacji wykorzystany może zostać jeden z trzech zarządców transakcji:

  • The Lightweight Transaction Manager (LTM) – wprowadzony w .NET 2.0 przez przestrzeń nazw System.Transaction (do projektu trzeba dodać assembly); poniższy przykład aktualizuje bazę danych w ramach lekkiej transakcji. W celu zgłoszenia transakcji wywołana jest metoda Complete:
  •    1: using (TransactionScope ts = new TransactionScope())
       2: {
       3:   using (SqlConnection cn1 = new SqlConnection(connectionString))
       4:   {
       5:     insertRecord(cn1, "User1");
       6:     using(SqlConnection cn2 = new SqlConnection(connectionString))
       7:     {
       8:       insertRecord(cn2, "User2");
       9:     }
      10:   }
      11:   ts.Complete();
      12: }
      13: private void insertRecord(SqlConnection cn, string userName)
      14: {
      15:   SqlCommand cmd = new SqlCommand(String.Format("Insert INTO [Users]" +" VALUES('{0}')", userName), cn);
      16:   cn.Open();
      17:   cmd.ExecuteNonQuery();
      18: }
  • OLE Transactions (OleTx),
  • WS-Atomic Transactions (WS-AT).

Transakcji możemy pozwolić na działanie poza granicami serwisu, lub nie. Decyzję o tym podejmują klient i serwis, jednak jeżeli serwis wymaga transakcji, blokowanie po stronie klienta spowoduje błąd aplikacji. Ustawione może to zostać w bindingu za pomocą atrybutu TransactionFlow, zarówno imperatywnie jak i deklaratywnie:

   1: // C#
   2: WSHttpBinding binding = new WSHttpBinding();
   3: binding.TransactionFlow = true;
   4:  
   5: <!--XML-->
   6: <bindings>
   7: <wsHttpBinding>
   8: <binding name="Transactional" transactionFlow="true" />
   9: </wsHttpBinding>
  10: </bindings>

 

 

Dodatkowo wymagane jest, aby operacje serwisu były oznaczone atrybutem TransactionFlow, który wskazuje na to, że mogą one brać udział w transakcji:

   1: [ServiceContract]
   2: public interface IDemoContract
   3: {
   4:   [OperationContract]
   5:   [TransactionFlow(TransactionFlowOption.Allowed)]
   6:   void TransactedMethod(...);
   7: }

Po stronie klienta w ten sam sposób zostanie udekorowana klasa proxy:

   1: public class DemoService : IDemoContract
   2: {
   3:   [TransactionFlow(TransactionFlowOption.Allowed)]
   4:   public void TransactedMethod(...)
   5:   {...}
   6: }

W powyższym przykładzie TransactionFlow ustawiony jest na wartość TransactionFlowOption.Allowed, która pozwala transakcji klienta przechodzić do serwisu. Domyślna opcja TransactionFlowOption.NotAllowed sprawia, że żadna transakcja nie jest przesyłana.

Uwaga: Transakcje nie działają w przypadku metod typu one-way – bez komunikatu zwrotnego nie ma możliwości stworzenia rozproszonej transakcji.

Tagi: , , , ,

70-503: Working with Instances

· Ten artykuł pochodzi z serii przygotowań do egzaminu 70-503: Windows Communication Foundation.

Wiemy już, że WCF może tworzyć oddzielne instancje klasy serwisu dla poszczególnych wywołań, dla poszczególnych sesji lub używać tylko jednej instancji do obsłużenia wszystkich klientów i ich wywołań.

Dzisiaj dowiemy się jak zarządzać poszczególnymi instancjami klasy serwisu.

Zabezpieczanie serwisu

W rzeczywistym świecie głównym problemem są ataki typu “Odmowa usługi” (ang. Denial of service). Ataki te powodują wyczerpanie się zasobów serwisu tak by go zablokować. WCF udostępnia kilka parametrów, którymi możemy kontrolować dostępność naszego serwisu.

Dławienie

Dławienie (ang. Throttling) po pierwsze zabezpiecza serwis przed przeciążeniem pod wpływem zasypywania wiadomościami, po drugie umożliwia wyrównanie obciążenia serwisu (i całego serwera). W obu przypadkach głównym celem jest ograniczenie ilości przetwarzanych wiadomości w jednostce czasu. Domyślne ustawienia WCF’a nie przewidują dławienia w ogóle. Jeśli włączymy dławienie, WCF sprawdza odpowiednie liczniki przy każdej nadchodzącej wiadomości. Jeśli liczniki zostały przekroczone wiadomości są zbierane do kolejki. Gdy wartości liczników spadną do akceptowalnej wartości, wiadomości z kolejki zostaną przekazane do serwisu.

Do ustawienia dławienia mamy dostępne trzy ustawienia w klasie ServiceThrottlingBehavior:

  • MaxConcurrentCalls – liczba jednoczesnych wywołań które serwis zaakceptuje, domyślnie 16,
  • MaxConcurrentSessions – maksymalna liczba kanałów wymagających obsługi sesji, które serwis obsłuży, domyślnie 10, każda kolejna próba utworzenia kanału zostanie zakończona wyjątkiem TimeoutException, to ustawienie nie dotyczy bindingów nie obsługujących sesji np. basicHttpBinding,
  • MaxConcurrentInstances – maksymalna liczba instancji obiektów implementujących serwis, domyślnie 32, w trybie “per call” wartość jest tym samym co MaxConcurrentCalls ponieważ każde wywołanie ma własną instancję, w trybie “per session”, wartość jest tym samym co MaxConcurrentSessions, dla singletona, ponieważ zawsze jest tylko jedna instancja obiektu obsługującego serwis, wartość ma znaczenie tylko gdy jest użyty IInstanceContextProvider.

Poniżej przykłady konfiguracji dławienia:

   1: <behaviors>
   2:     <serviceBehaviors>
   3:         <behavior name="throttlingBehavior">
   4:             <serviceThrottling maxConcurrentCalls="10"
   5:                 maxConcurrentInstances="10"
   6:                 maxConcurrentSessions="5"/>
   7:         </behavior>
   8:     </serviceBehaviors>
   9: </behaviors>
   1: ServiceHost host = new ServiceHost( typeof(UpdateService),
   2: new Uri("http://localhost:8080/UpdateService"));
   3: host.AddServiceEndpoint( "IUpdateService",
   4:     new WSHttpBinding(), String.Empty);
   5: ServiceThrottlingBehavior throttlingBehavior = new ServiceThrottlingBehavior();
   6: throttlingBehavior.MaxConcurrentCalls = 10;
   7: throttlingBehavior.MaxConcurrentInstances = 10;
   8: throttlingBehavior.MaxConcurrentSessions = 5;
   9: host.Description.Behaviors.Add(throttlingBehavior);
  10: host.Open();

Tak jak zostało wspomniane, przekroczenie limitów spowoduje wyrzucenie wyjątku TimeoutException u klienta. Ponieważ w każdym przypadku jest to ten sam wyjątek, trzeba przyjrzeć się dokładnie miejscu jego wystąpienia. Jeśli problem dotyczy liczby jednoczesnych sesji (MaxConcurrentSessions) wyjątek będzie wyrzucany przy wywołaniu metody SendPreamble. Jeśli wyjątek jest wyrzucany przy metodzie Send, bardziej prawdopodobnym jest problem z maksymalną liczbą wywołań (MaxConcurrentCalls).

Jeszcze mała uwaga. Dobrze jest konfigurować dławienie z poziomu kodu. Konfigurowanie dławienia w pliku konfiguracyjnym umożliwia administratorowi serwera ustawienie tych opcji np. dopiero gdy będzie tego potrzebował.

Odczytywanie ustawień dławienia

Istnieje możliwość odczytania (ale nie aktualizacji) aktualnych ustawień dławienia po uruchomieniu serwisu.

Klasa ServiceHost udostępnia kolekcję dyspozytorów (ang. dispatchers) w właściwości ChannelDispatchers. Jest to kolekcja obiektów typu ChannelDispatcher. Każdy z nich ma właściwość ServiceThrottle. Przez tą właściwość mamy dostęp do ustawień dławienia, w tym MaxConcurrentCalls, MaxConcurrentInstances i MaxConcurrentSessions. Poniżej przykład kodu:

   1: ChannelDispatcher dispatcher =
   2:     OperationContext.Current.Host.ChannelDispatchers[0] as ChannelDispatcher;
   3: ServiceThrottle throttle = dispatcher.ServiceThrottle;
   4: Trace.WriteLine(String.Format("MaxConcurrentCalls = {0}",
   5:     throttle.MaxConcurrentCalls));
   6: Trace.WriteLine(String.Format("MaxConcurrentSessions = {0}",
   7:     throttle.MaxConcurrentSessions));
   8: Trace.WriteLine(String.Format("MaxConcurrentInstances = {0}",
   9:     throttle.MaxConcurrentInstances));

Limity

WCF umożliwia ustawienie limitu (ang. quota) na ilość pamięci wykorzystywanej przez host serwisu i implementujące go obiekty. Zabezpiecza to przed nadmiernym alokowaniem pamięci. Każde kolejne wywołania które spowodują brak możliwości zaalokowania pamięci do obsługi tych wywołań zakończą sie wyjątkiem OutOfMemoryException lub StackOverflowException.

Jeśli ustawimy limity to po przekroczeniu limitu będzie wyrzucany wyjątek QuotaExceededException a wiadomość jest po prostu pomijana i serwis przechodzi do obsługi kolejnej.

Mamy kilka dostępnych limitów:

MaxReceivedMessageSize

– maksymalna wielkość wiadomości, domyślnie 65536 bajtów

<bindings>
    <netTcpBinding>
        <binding name="netTcp"
            maxReceivedMessageSize="128000" />
    </netTcpBinding>
</bindings>
NetTcpBinding binding = new NetTcpBinding();
binding.MaxReceivedMessageSize = 128000;
ServiceHost host = new ServiceHost( typeof(UpdateService),
    new Uri("net.tcp://localhost:1234/UpdateService"));
host.AddServiceEndpoint( "IUpdateService",
    binding, String.Empty);
host.Open();
ReaderQuotas

– limity złożoności wiadomości,

Obiekt ReadeQuotas ma kilka właściwości, które umożliwiają określenie maksymalnej złożoności wiadomości:

  • MaxDepth – maksymalna złożoność (głębokość zagnieżdżenia XML’a reprezentującego wiadomość), domyślnie 32
  • MaxStringContentLength – maksymalna długość wartości typu string w wiadomości (wartości lub atrybutu, lub tekstu wewnątrz znacznika), domyślnie 8192 bajty,
  • MaxArrayLength – maksymalna liczba elementów w pojedynczej tablicy, domyślnie 16384,
  • MaxBytesPerRead – maksymalna liczba bajtów zwracanych przy metodzie Read podczas przetwarzania wiadomości, domyślnie 4096 bajtów,
  • MaxNameTableCharCount – maksymalna liczba znaków w nazwie tablicy, domyślnie 16384 znaków.

Może ograniczenie liczby zwracanych bajtów w metodzie Read wydaje się dziwne, ale służy to zabezpieczeniu przeciw atakom DoS, których wiadomość zawiera bardzo długie rozpoczynające tagi w wiadomości. Ponieważ do przetworzenia konieczne jest wczytanie całego taga do pamięci, ograniczenie tego zapobiega przepełnieniom.

Rozgraniczenia operacji

Teoretycznie obsługa sesji gwarantuje nam, że wiadomo które wiadomości przychodzą od którego klienta. Sesja umożliwia też zapamiętywanie stanu pomiędzy poszczególnymi wywołaniami. A czy mamy gwarancję że wywołania przyjdą w odpowiedniej kolejności? Nie! Wystarczy że dwie metody będą wywoływane w różnych wątkach i już mamy zachwianie kolejności. Protokół HTTP w ogóle nie gwarantuje kolejności przesyłanych wiadomości.

WCF udostępnia mechanizm oznaczania, które metody nie mogą być wywoływane jako pierwsze lub jako ostatnie przez ustawienie właściwości IsInitiating i IsTerminating w atrybucie OperationContract.

Jeśli IsInitiating jest ustawiony na true w momencie gdy sesja nie została utworzona,sesja jest tworzona. Jeśli sesja już istnieje, metoda jest wywoływana w ramach tej sesji.

Jeśli IsTerminating jest ustawiony na true, gdy metoda jest wywoływana, sesja jest kończona. Nie oznacza to tego samego co zakończenie połączenia, klient nadal musi wywołać metodę Close na obiekcie proxy aby zamknąć połączenie. Każde kolejne próby wywoływania metod serwisu po zakończeniu sesji będą generować wyjątki InvalidOperationException.

Domyślne wartości dla tych właściwości to true dla IsInitiating i false dla IsTerminating.

Poniżej przykład kontraktu z odpowiednimi wartościami tych atrybutów:

[ServiceContract(SessionMode = SessionMode.Required)]
public interface IProcessOrders
{
    [OperationContract]
    void InitializeOrder(int customerId);
 
    [OperationContract(IsInitiating=false)]
    void AddOrderLine(string productId, int quantity);
 
    [OperationContract(IsInitiating=false)]
    double GetOrderTotal();
    
    [OperationContract(IsInitiating=false, IsTerminating=true)]
    bool SubmitOrder();
}

Z powyższych czterech metod tylko InitializeOrder może rozpocząć sesję, więc każda inna metoda wywołana przed nią spowoduje wyrzucenie wyjątku OperationException. Wywołanie metody SubmitOrder spowoduje zakończenie sesji, żadna inna metoda nie może już być wywołana w tym połączeniu.

Aby użyć tych właściwości serwis musi obsługiwać sesję (przez tryb “per session”, albo “singleton”).

Deaktywowanie instancji

Gdy rozpoczynana jest nowa sesja WCF tworzy nowy kontekst (Context) i w ramach tego kontekstu tworzone są instancje obiektów obsługujące wywołania. WCF pozwala nawet utworzyć kontekst bez instancji. Przy pomocy właściwości ReleaseInstanceMode atrybutu OperationBehavior możemy decydować kiedy instancje mają być zwalniane. Dostępne są następujące opcje:

  • BeforeCall – nowa instancja jest tworzona z początkiem wywołania, jeśli instancja już istnieje, jest deaktywowana i wywoływała jest jej metoda Dispose, klient jest w tym czasie zablokowany,
  • AfterCall – aktualna instancja jest deaktywowana i zwalniana gdy metoda jest zakończona,
  • BeforeAndAfterCall – aktualna instancja jest zwalniana przed wywołaniem, tworzona jest nowa, która zaraz po zakończeniu metody jest zwalniana,
  • None – instancja nie jest zwalniana między wywołaniami metod, wartość domyślna.
public class UpdateService : IUpdateService
{
    [OperationBehavior(ReleaseInstanceMode=ReleaseInstanceMode.BeforeAndAfterCall)]
    public void Update()
    {
        // Implementation code goes here
    }
}

Możemy także zwolnić instancję zaraz po zakończeniu aktualnej metody przez :

OperationContext.Current.InstanceContext.ReleaseServiceInstance();

W kolejnych artykułach opowiemy o obsłudze transakcji.

Tagi: , , , , ,

Eastgroup.pl na facebooku