Асинхронные страницы

relief
Дата: 18.05.2010 22:44:55
Возник такой вопрос после прочтения статьи где написано про асинхронные страницы (async=true)

автор
Когда появляется запрос, ASP.NET назначает ему поток. Запрос начинает выполняться в этом потоке, а когда доходит до обращения к базе данных, происходит вызов асинхронного запроса ADO.NET, а поток возвращается в пул. По завершении запроса к базе данных ADO.NET выполняет обратный вызов к ASP.NET, ASP.NET выбирает другой поток из пула, и обработка запроса продолжается.


вопрос вот какой: т.е. сначала мы работаем с одним потоком до вызова работы с базой, а после окончания работаем с другим потоком?
Если, да, то как как, к примеру происходит работа с сессией?
Ведь контекст привязывается к потоку. И как собственно происходит "подсовывание" второго потока?
qu-qu
Дата: 19.05.2010 00:10:32
relief
...
Если, да, то как как, к примеру происходит работа с сессией?
...

Так же, как и везде: асинхронный обработчик void EndAsyncOperation(IAsyncResult ar) - это собственный метод класса MyAsyncPage : System.Web.UI.Page, который имеет от базового класса свойство Session доступное из любого потока.

relief
...
Ведь контекст привязывается к потоку.
...

С чего бы, вдруг?
Контекст (если вы про экземпляр HttpContext) всегда "живет" в потоке, который обрабатывает "жизненный цикл страницы" (pipe-line), именно оттуда он в качестве аргументов попадает в асинхронные обработчики IHttpAsyncHandler/IHttpModule, а оттуда уже может попасть "внутрь" потоковых методов (тоже в качестве аргумента).
(в любом другом потоке, кроме обрабатывающего pipe-line, HttpContext.Current == null, проверено опытом, хотя Page.Context может быть доступен из любого потока, но к IHttpAsyncHandler/IHttpModule это не относится).
relief
...
И как собственно происходит "подсовывание" второго потока?

Да так и происходит: поток, обрабатывающий pipe-line, цепляет callback-и при вызове AddOnPreRenderCompleteAsync или AddOnPreRequestHandlerExecuteAsync, а ThreadPool - сам разбирается в каком потоке потом эти callback-и "дергать"...
relief
Дата: 19.05.2010 08:24:32
qu-qu,

автор
Да так и происходит: поток, обрабатывающий pipe-line, цепляет callback-и при вызове AddOnPreRenderCompleteAsync или AddOnPreRequestHandlerExecuteAsync, а ThreadPool - сам разбирается в каком потоке потом эти callback-и "дергать"...


что-то я не понимаю. Вот поступил от меня запрос на страницу, пул asp.net выделил поток для моего запроса, дошло до вызова работы с базой, создался отдельный поток вне asp.net пула, который работает с базой, потом ado.net делает обратный вызов к asp.net, который в свою очередь выделил новый поток из пула. Так?
Из того, что ты написал я так понял, что есть поток, который обрабатывает жизненный цикл страницы, а есть поток из пула asp.net. Верно? Где почитать про разницу между ними? А то в той же статье написано

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

т.е. при начале асинхронной работы поток обрабатывавший запрос возвращается в пул asp.net
qu-qu
Дата: 19.05.2010 13:38:49
relief
qu-qu,

... Вот поступил от меня запрос на страницу, пул asp.net выделил поток для моего запроса, дошло до вызова работы с базой, создался отдельный поток вне asp.net пула, который работает с базой, потом ado.net делает обратный вызов к asp.net, который в свою очередь выделил новый поток из пула. Так?
...
т.е. при начале асинхронной работы поток обрабатывавший запрос возвращается в пул asp.net

Так, вот картиночка (из другой статьи на ту же тему), на ней даже нарисовано, что поток 1 начинает обработку ЖЦС, а поток 2 - заканчивает...
qu-qu
Дата: 19.05.2010 14:11:18
qu-qu,

Поскольку из картиночки выше не особо ясно - где же исполняется собственно асинхронная операция (тот метод, ждать который не хочется на очереди обработки запросов), то вот небольшой примерчик, который все отлично иллюстрирует:
   public partial class _Default : System.Web.UI.Page
   {
      private IDictionary<string, int> _map = new Dictionary<string, int>();

      protected void Page_Load(object sender, EventArgs e)
      {
         AddOnPreRenderCompleteAsync(new BeginEventHandler(BeginAsync), new EndEventHandler(EndAsync));
         _map.Add("Page_Load", System.Threading.Thread.CurrentThread.ManagedThreadId);
      }

      protected IAsyncResult BeginAsync(object sender, EventArgs e, AsyncCallback cb, object state)
      {
         Action invoker = new Action(DoSomethigLongTime);
         _map.Add("BeginAsync", System.Threading.Thread.CurrentThread.ManagedThreadId);
         IAsyncResult res = invoker.BeginInvoke(cb, state);
         System.Threading.Thread.Sleep(500);
         return res;
      }

      private void DoSomethigLongTime()
      {
         System.Threading.Thread.Sleep(500);
         _map.Add("DoSomethigLongTime", System.Threading.Thread.CurrentThread.ManagedThreadId);
      }

      protected void EndAsync(IAsyncResult ar)
      {
         _map.Add("EndAsync", System.Threading.Thread.CurrentThread.ManagedThreadId);
      }

      protected override void OnPreRenderComplete(EventArgs e)
      {
         base.OnPreRenderComplete(e);
         _map.Add("OnPreRenderComplete", System.Threading.Thread.CurrentThread.ManagedThreadId);
         grid.DataSource = _map;
         grid.DataBind();
      }
   }
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="AsyncWebApplication1._Default" Async= "true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:DataGrid runat="server" ID="grid" />
    </div>
    </form>
</body>
</html>
KeyValue
Page_Load4
BeginAsync4
DoSomethigLongTime10
EndAsync10
OnPreRenderComplete10

Как видите - ThreadPool даже не заморачивается на выделение отдельного потока для "долгоиграющего" метода, и отдельного - для окончания жизненного цикла страницы...