Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Все статьи »

Обсудить на форуме Обсудить на форуме

Асинхронное выполнение кода в ASP.NET 2.0
Время выполнения Web-приложения складывается из нескольких составляющих: времени пересылки данных между клиентом и сервером, времени обработки входных данных средой и времени выполнения программного кода. Если на первые два фактора повлиять можно только уменьшением размера пересылаемых и обрабатываемых данных, то на последний фактор целиком зависит от разработчика. Один из путей повышения производительности состоит в распараллеливании задач. В этой статье будет рассказано об основах асинхронного

Гайдар Магдануров
Асинхронное выполнение кода в ASP.NET 2.0

Введение

Пользователи любят приложения которые работают быстро. Пользователю все равно, какое приложение перед ним - Windows Forms или Web приложение, ему даже все равно какова скорость его соединения с сервером, ему важно одно, чтобы ответ на его действия пришел как можно скорее. И если пользователь иногда делает небольшую скидку на некоторую заторможенность откликов приложения когда использует dial-up соединение, но имея ADSL или выделенную линию, пользователь будет недоволен.
    Если вы пропустили первый абзац, то ничего не потеряли, дорогой читатель, поскольку вам и без меня прекрасно известно, что приложение должно работать так быстро на сколько это возможно в каждой конкретной ситуации, то есть код и используемые технологии должны удерживать тонкое равновесие между временем разработки и скоростью выполнения (устойчивость и безопасность, разумеется, должна быть максимальной в не зависимости от времени и суммы, уплаченной за разработку).
    Время выполнения Web-приложения складывается из нескольких составляющих: времени пересылки данных между клиентом и сервером, времени обработки входных данных средой и времени выполнения программного кода. Если на первые два фактора повлиять можно только уменьшением размера пересылаемых и обрабатываемых данных, то на последний фактор целиком зависит от разработчика. Один из путей повышения производительности состоит в распараллеливании задач. В этой статье будет рассказано об основах асинхронного выполнения кода в ASP.NET 2.0.

Как это работает

Рассмотрим, что происходит при запросе ASP.NET страницы. О жизненном цикле страницы можно прочитать в этой статье, здесь же мы рассмотрим несколько более низкий уровень: обработка запроса на уровне инфраструктуры. При поступлении запроса к странице, подсистема ASP.NET обращается в пул нитей (thread - подпроцесс, запускаемый другим процессом в адресном пространстве этого (запускающего) процесса - перевод ABBYY Lingvo 11) и получает нить, которую сопоставляет запросу. В случае, если при обработке запроса выполняется вызов, требующий значительного времени для выполнения, то обработка приостанавливается, а нить простаивает в ожидании завершения вызова. Таким вызовом может быть считывание или создание файла, вызов Web-службы, соединение с базой данных или получение Web-страницы или XML файла с удаленного сервера, в любом случае получается, что исполнение последующего кода зависит от времени выполнения запроса. При этом, стоит учесть, что количество доступных нитей ограничено и в случае, если все нити будут заняты, то запрос будет поставлен в очередь на получение свободной нити. Можно представить еще более страшную картинку - при переполнении очереди запросов пользователь получит отказ в обработке, что явно не придаст ему уверенности в устойчивости и надежности приложения. А ведь может так случится, что это будет начальник и программист, а то и целый отдел останется без премии. Согласитесь, фильмы ужасов не так ужасны, по сравнению с таким событием.
    Отличие асинхронной обработки от описанного выше сценария состоит в том, что после начала асинхронной операции нить возвращается в пул, а по завершении вызова ASP.NET получает из пула другую нить для завершения запроса. Выгода очевидна, нить, которая в случае синхронной обработки будет простаивать и ожидать завершения некоторого требующего времени вызова, может быть использована для обработки другого запроса - возрастает эффективность работы среды с пулом нитей, а значит возрастает производительность приложения и уменьшается возможность пользователя получить отказ от обслуживания.
    В ASP.NET 1.x реализация распараллеливания задач была возможна, но требовала значительных усилий от разработчика. Поскольку предоставляемые средой ASP.NET 1.x возможности по созданию асинхронной обработки были весьма скудные, то разработчику приходилось создавать собственные решения, затрудняющие отладку и снижающие скорость разработки. К счастью, в ASP.NET 2.0 ситуация изменилась к лучшему. Асинхронное выполнение кода в ASP.NET 2.0 стало доступно даже начинающему разработчику!

Асинхронные операции и жизненный цикл страницы

Сравним жизненный цикл синхронной и асинхронной ASP.NET страниц (так мы будем называть страницы, реализующие интерфейсы IHttpHandler и IHttpAsyncHandler, соответственно). Различия в жизненном цикле синхронной и асинхронной страниц проявляются после возникновения события PreRender. В случае асинхронной страницы на этапе PreRender вызывается асинхронный код, а нить, назначенная средой выполнения для обработки запроса к странице, возвращается в пул нитей. По завершении асинхронной операции среда выполнения получает из пула нитей новую нить и передает ей управление. Еще раз стоит отметить, что в случае асинхронной страницы, назначенная для выполнения запроса нить возвращается в пул нитей и может обслуживать другие запросы в то время, пока происходит выполнение асинхронного запроса.

Синхронная страница

Асинхронная страница

Реализация асинхронной страницы

Все начинается с установки атрибута Async директивы @Page. Если этот атрибут установлен, то динамически генерируемый класс страницы будет реализовывать интерфейс IHttpAsyncHandler. Атрибут AsyncTimeout указывает максимальное количество времени в секундах, отведенное на выполнение асинхронных операций.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" 
    Inherits="_Default" Async="true" AsyncTimeout="30" %>

После того как страница "готова" к использованию асинхронных вызовов, необходимо реализовать код и зарегистрировать его для асинхронного выполнения. Здесь у программиста существует несколько возможных вариантов.

1. Метод AddOnPreRenderCompleteAsync

public void AddOnPreRenderCompleteAsync(BeginEventHandler beginHandler, EndEventHandler endHandler);

Метод AddOnPreRenderCompleteAsync вызывается в обработчике события Load страницы и предназначен для регистрации двух обработчиков начала и конца асинхронной обработки.

protected void Page_Load(object sender, EventArgs e)
{
    AddOnPreRenderCompleteAsync(new BeginEventHandler(BeginAsync), new EndEventHandler(EndAsync));
}

Метод BeginAsync вызывается средой выполнения ASP.NET после события PreRender и задача этого метода в том, чтобы начать асинхронную операцию.

public IAsyncResult BeginAsync(object sender, EventArgs e, AsyncCallback callback, object state)
{
  ds = new localhost.DataService();
  ds.Url = new Uri(Request.Url, "WebServices/DataService.asmx").ToString();
  return ds.BeginGetLoginNames(callback, state);
}

При этом нить, использованная для обработки запроса, возвращается в пул нитей. После завершения асинхронной операции (при условии, что не происходит тайм-аут) из пула нитей извлекается нить, вызывается метод EndAsync, происходит событие PreRenderComplete и продолжается обработка запроса.

public void EndAsync(IAsyncResult asyncResult)
{
  string res = ds.EndGetLoginNames(asyncResult);
  string[] users = res.Split('\n');
  for (int i = 0; i < users.Length; i++ )
  {
    string[] user = users[i].Split(':');
    if(user.Length == 3)
      phNames.Controls.Add(new LiteralControl("<p><b>" + user[1] + "</b> ID: " + user[0] + " Name: " + user[2] + "</p>"));
  }
}

Для того, чтобы проверить, что это происходит именно так, в код методов BeginAsync и EndAsync нужно добавить вызов Trace.Write() и включить трассировку для страницы (атрибут Trace директивы Page установить в true, если кто-то забыл как это делается).

 

2. Метод RegisterAsyncTask

public void RegisterAsyncTask(PageAsyncTask task);

Метод RegisterAsyncTask используется, если необходимо провести более одной асинхронной операции. Более того,  RegisterAsyncTask позволяет установить обработчик, который будет вызван если асинхронная операция не уложится по времени в установленное атрибутом AsyncTimeout время, а также позволяет передать в делегат, используемый для реализации асинхронного вызова сведения о состояния и указать, может ли задача выполнятся параллельно с другими поставленными в очередь асинхронными задачами. Подробное описание класса PageAsyncTask можно посмотреть в MSDN. В случае, когда указанное выше не требуется, можно использовать AddOnPreRenderCompleteAsync и избежать написания лишнего кода.

PageAsyncTask task1 = new PageAsyncTask(new BeginEventHandler(BeginAsyncText), 
new EndEventHandler(EndAsyncText), 
new EndEventHandler(Async_OnTimeout), null);
PageAsyncTask task2 = new PageAsyncTask(new BeginEventHandler(BeginAsyncDS), 
new EndEventHandler(EndAsyncDS), 
new EndEventHandler(Async_OnTimeout), null);
RegisterAsyncTask(task1);
RegisterAsyncTask(task2);

3. Возможности прокси-класса Web-службы для асинхронного вызова Web-службы

Прокси-класс Web-службы позволяет использовать собственную событийную модель для асинхронного вызова. Например, класс Web-службы, использованной в примере, предоставляет следующие методы и свойства.

  public event GetDataSetCompletedEventHandler GetDataSetCompleted;
  public event GetLoginNamesCompletedEventHandler GetLoginNamesCompleted;

  public IAsyncResult BeginGetDataSet(AsyncCallback callback, object asyncState);
  public IAsyncResult BeginGetLoginNames(AsyncCallback callback, object asyncState);
  public void CancelAsync(object userState);
  public DataSet EndGetDataSet(IAsyncResult asyncResult);
  public string EndGetLoginNames(IAsyncResult asyncResult);
  public DataSet GetDataSet();
  public void GetDataSetAsync();
  public void GetDataSetAsync(object userState);
  public string GetLoginNames();
  public void GetLoginNamesAsync();
  public void GetLoginNamesAsync(object userState);

Как видно из приведенного листинга, класс Web-службы предоставляет события Completed, отвечающие завершению выполнения запросов к Web-службе. А также методы Async, инициирующие асинхронное выполнение. Таким образом, асинхронный вызов к Web-службе может быть реализован так, как это показано в приведенном ниже листинге.

protected void Page_Load(object sender, EventArgs e)
{
  ds = new localhost.DataService();
  ds.Url = new Uri(Request.Url, "WebServices/DataService.asmx").ToString();
  ds.GetLoginNamesCompleted += new localhost.GetLoginNamesCompletedEventHandler(ds_GetLoginNamesCompleted);
  ds.GetLoginNamesAsync();
}

void ds_GetLoginNamesCompleted(object sender, localhost.GetLoginNamesCompletedEventArgs e)
{
  string res = e.Result;
  string[] users = res.Split('\n');
  for (int i = 0; i < users.Length; i++)
  {
    string[] user = users[i].Split(':');
    if (user.Length == 3)
       phNames.Controls.Add(new 
         LiteralControl("<p><b>" + user[1] + "</b> ID: " + 
         user[0] + " Name: " + user[2] + "</p>"));
  }
}

У методов Async класса Web-службы также есть дополнительные преимущества перед RegisterAsyncTask и AddOnPreRenderCompleteAsync, поскольку в обработчике события Completed доступны объекты контекста выполнения.

HttpRequest rqst = ((DataService)sender).Context.Request;

Заключение

В этой статье было рассказано о реализации асинхронной обработки в ASP.NET 2.0. Как можно было увидеть, в создании асинхронных методов нет ничего сложного. Код примеров, использованных в статье, прилагается.

 

Дополнительная литература

  1. Знакомство с ASP.NET 2.0

  2. Programming the Thread Pool in the .NET Framework

  3. PageAsyncTask Class


Может пригодится:


Автор: gaidar
Прочитано: 4101
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру