Asynchroniczne strony ASP .NET

W celu wyjaśnienia zasady działania asynchronicznych stron, najpierw przyjrzyjmy się jak wygląda standardowe zapytanie do serwera. Klient wysyła żądanie HTTP do serwera np. typu GET w celu uzyskania danej strony www. Następnie serwer używa tzw. puli wątków (thread pool). Po prostu przydziela wątek z puli każdemu nadchodzącemu żądaniu. Tworzenie (a raczej odtwarzanie) wątków z puli jest szybkie (o tym już pisałem kiedyś), jednak liczba wątków jest ograniczona. W przypadku gdy serwer będzie musiał obsłużyć zbyt dużą liczbę żądań, zakończy się to błędem “503 – server unavaiable”. Obsługa większości zapytań nie stanowi problemu, jednak gdy aplikacja ASP .NET korzysta z usługi sieciowej lub przetwarza ogromną ilość danych może to stanowić poważne zredukowanie skalowalności.Wyobraźmy sobie, że nasza aplikacja www korzysta z usługi sieciowej. Ponadto połączenie z usługa jest dość wolne – trwa 30 sekund. Gdy przyjdzie zapytanie do serwera, zostanie zdjęty wątek z puli i będzie on zarezerwowany przez ponad 30 sekund (czas połączenia z usługą + inne operacje). Przez ten cały czas wątek nie będzie mógł być wykorzystany do obsługi innych żądań. Co gorsza, wątek większość czasu straci po prostu na oczekiwanie odpowiedzi od usługi sieciowej. Należy podkreślić, że wątki ASP .NET są zbyt cenne aby marnować je na operacje typu IO. Jeśli w systemie rzeczywiście musimy połączyć się z zewnętrznymi zasobami, zróbmy to asynchronicznie!

W przypadku asynchronicznych stron po odebraniu żądania również jest zdejmowany wątek z puli. Jednak zaraz po stwierdzeniu, że mamy do czynienia z asynchroniczną stroną www, wątek jest z powrotem oddawany puli. W tym momencie rozpoczyna się wykonywanie asynchronicznego kodu – czyli np. ładowania danych z usługi sieciowej czy z bazy danych. Po zakończeniu, wątek z powrotem jest zdejmowany z puli i rozpoczyna się już normalne przetwarzanie strony www. Innymi słowy, używamy asynchronicznego przetwarzania dla bardzo czasochłonnych operacji (przeważnie są to operacje typu IO). Na poniższym rysunku przedstawiam standardowy przebieg obsługi żądania w stronach synchronicznych:

 

 

image

 

 

Nie ma tu nic nadzwyczajnego. W przypadku asynchronicznych stron, wątek z puli będzie zwolniony w pewnym momencie i sterowanie zostanie przekazane odpowiednim asynchronicznym metodom:

 

 

image

Przejdźmy teraz do implementacji tego w ASP .NET. W wersji 2.0 zostało to znacząco uproszczone. Przede wszystkim należy ustawić atrybut Async na true w dyrektywie @Page:

<%@ Page Async="true" Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>

Ustawienie atrybutu na true tak naprawdę oznacza, że zostanie wykorzystany  IHttpAsyncHandler.

Aby rozpocząć część asynchroniczną strony należy wywołać metodę AddOnPreRenderCompleteAsync przekazując odpowiednie handlery:

protected void Page_Load(object sender, EventArgs e)
{
  AddOnPreRenderCompleteAsync(new BeginEventHandler(BeginAsyncOperation), new EndEventHandler(EndAsyncOperation));
}
private IAsyncResult BeginAsyncOperation(object sender, EventArgs e,
AsyncCallback cb, object state)
{
  m_Request = WebRequest.Create("http://www.pzielinski.com");
  return m_Request.BeginGetResponse(cb, state);
}
private void EndAsyncOperation(IAsyncResult ar)
{
  string text;
  using (WebResponse response = this.m_Request.EndGetResponse(ar))
  {
      using (StreamReader reader =
          new StreamReader(response.GetResponseStream()))
      {
          text = reader.ReadToEnd();
      }
  }
   Response.Write(text);
}

W podobny sposób można pobrać dane z bazy danych i przekazać je odpowiedniej kontrolce renderującej (np. ListView).

Ponadto w ASP .NET można definiować tzw. zadania asynchroniczne. Sprawa wygląda podobnie jak w przypadku powyższego kodu. Zadania asynchroniczne wprowadzają dodatkowo kilka rozszerzeń:

  1. Oprócz metod BEGIN i END można zdefiniować metodę odpowiedzialną za wykonanie kodu w razie timeout. Wartość timeout można zdefiniować również w dyrektywie @Page za pomocą atrybutu AsyncTimeout.
  2. Można rejestrować kilka zadań  – dopiero po zakończeniu wszystkich, zostanie wznowione przetwarzanie synchroniczne.
  3. Rejestrując zadanie można dodatkowo przekazać jakiś argument jako object (wykorzystywany w metodach BEGIN) .

Przykład:

protected void Page_Load(object sender, EventArgs e)
{
  RegisterAsyncTask(new PageAsyncTask(BeginAsyncOperation, EndAsyncOperation, TimeoutOperation, "state"));
}
private IAsyncResult BeginAsyncOperation(object sender, EventArgs e,
AsyncCallback cb, object state)
{
  m_Request = WebRequest.Create("http://www.pzielinski.com");
  return m_Request.BeginGetResponse(cb, state);
}
private void EndAsyncOperation(IAsyncResult ar)
{
  string text;
  using (WebResponse response = this.m_Request.EndGetResponse(ar))
  {
      using (StreamReader reader =
          new StreamReader(response.GetResponseStream()))
      {
          text = reader.ReadToEnd();
      }
  }
  Response.Write(text);
}
private void TimeoutOperation(IAsyncResult ar)
{

}

3 thoughts on “Asynchroniczne strony ASP .NET”

  1. Czy jest coś podobnego dla WebService? Mam metodę WebService, która generuje wynik łącząc się z usługą sieciową, która czasami bardzo długo generuje odpowiedź. Tak naprawdę moja metoda nie robi nic innego jak oczekuje wyniku. Analogia do Twojego przypadku wydaje się być podobna. Ciekawe tylko czy jest taka możliwość w WebService?

    Ps. Ciekawe, czy zastosowanie asynchronicznych stron zwiększa wydajność aplikacji / zwiększa maksymalną liczbę żądań? Wiadomo coś więcej na ten temat?

  2. Tak, oprócz baz danych, web servicy są jednym z głównych powodów powstania asynchronicznych stron.
    Moim zdaniem, Twój scenariusz nawet bardziej potrzebuje stron asynchronicznych ponieważ przeważnie usługa sieciowa nie znajduje się w tej samej sieci co serwer ASP .NET, stąd proces łączenia się jest dość długi. W przypadku baz danych, przeważnie są one lokalne, w tej samej sieci (jeśli miałyby być na zewnątrz wtedy koniecznie należałoby wprowadzić web service).

    Podczas generowania proxy masz możliwość wygenerowania również metod asynchronicznych (Begin i End). Zwracają one IAsyncResult, więc korzystanie z nich jest takie same jak w przypadku DB (wrzucasz je np. do RegisterAsyncTask).

    Jeśli chodzi o P.S, to może nie tyle co wydajność a skalowalność – Twoja strona będzie mogła obsłużyć większą liczbę użytkowników.Raczej sam czas generowania odpowiedzi nie zmieni się. Jednak liczba użytkowników serwisu, która może jednocześnie przeglądać stronę wzrośnie.

  3. @michał
    Podczas dodawania webreference do usługi web service, tworzone są asynchroniczne odpowiedniki dla każdej metody. Np: GetData ma metodę GetDataAsync oraz zdarzenie GetDataCompleted. Wywołujesz metode asynchornicznie, obsługujesz zdarzenie ukończenia i wszystko ładnie śmiga…

Leave a Reply

Your email address will not be published.