Почему Session_Start в Global.asax.cs вызывает проблемы с производительностью?

Когда я создаю пустой обработчик Session_Start в Global.asax.cs, это вызывает значительный удар при отображении страниц в браузере.

Как воспроизвести:

Создайте пустое веб-приложение ASP.NET MVC 3 (я использую MVC 3 RC2). Затем добавьте контроллер Home с этим кодом:

public class HomeController : Controller
{
  public ActionResult Index()
  {
    return View();
  }
  public ActionResult Number(int id)
  {
    return Content(id.ToString());
  }
}

Затем создайте представление Home/Index.cshtml и поместите следующее в раздел BODY:

@for (int n = 0; n < 20; n++)
{ 
  <iframe src="@Url.Content("~/Home/Number/" + n)" width=100 height=100 />
}

Когда вы запустите эту страницу, вы увидите, что на странице появилось 20 КАДРОВ, каждая с номером внутри. Все, что я делаю здесь, это создание страницы, которая загружает еще 20 страниц за кулисами. Прежде чем продолжить, обратите внимание на то, как быстро загружаются эти 20 страниц (обновите страницу несколько раз, чтобы повторить загрузку).

Затем перейдите к вашему Global.asax.cs и добавьте этот метод (да, тело метода пусто):

protected void Session_Start()
{
}

Теперь запустите страницу снова. На этот раз вы заметите, что 20 IFRAME загружаются намного медленнее, один за другим, с интервалом около 1 секунды. Это странно, потому что мы на самом деле ничего не делаем в Session_Start ... это просто пустой метод. Но этого, кажется, достаточно, чтобы вызвать замедление на всех последующих страницах.

Кто-нибудь знает, почему это происходит, и еще лучше, у кого-нибудь есть решение / обходной путь?

Обновить

Я обнаружил, что это происходит только тогда, когда отладчик подключен (работает с F5). Если вы запустите его без подключенного отладчика (Ctrl-F5), значит, все в порядке. Так что, может быть, это не значительная проблема, но все же странно.

2 ответа

Решение

tl; dr: Если вы столкнулись с этой проблемой при работе с веб-формами и не нуждаетесь в праве записи в состояние сеанса на этой конкретной странице, добавьте EnableSessionState="ReadOnly" на ваш @Page директива помогает.


Видимо, существование Session_Start само по себе принуждает ASP.NET последовательно выполнять все запросы, исходящие из одной и той же сессии. Это, однако, может быть исправлено постранично, если вам не нужен доступ для записи в сеансе (см. Ниже).

Я создал свой собственный тестовый параметр с помощью Webforms, который использует страницу aspx для доставки изображений.1

Вот тестовая страница (обычный HTML, стартовая страница проекта):

<!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><title></title></head>
<body>
    <div>
        <img src="GetImage.aspx?text=A" />
        <img src="GetImage.aspx?text=B" />
        <img src="GetImage.aspx?text=C" />
        <img src="GetImage.aspx?text=D" />
        <img src="GetImage.aspx?text=E" />
        <img src="GetImage.aspx?text=F" />
        <img src="GetImage.aspx?text=G" />
        <img src="GetImage.aspx?text=H" />
        <img src="GetImage.aspx?text=I" />
        <img src="GetImage.aspx?text=J" />
        <img src="GetImage.aspx?text=K" />
        <img src="GetImage.aspx?text=L" />
    </div>
</body>
</html>

Вот страница aspx (GetImage.aspx):

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="GetImage.aspx.cs" Inherits="CsWebApplication1.GetImage" %>

И соответствующие части кода позади (GetImage.aspx.cs, using а также namespace пропущено):

public partial class GetImage : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Debug.WriteLine("Start: " + DateTime.Now.Millisecond);
        Response.Clear();
        Response.ContentType = "image/jpeg";

        var image = GetDummyImage(Request.QueryString["text"]);
        Response.OutputStream.Write(image, 0, image.Length);
        Debug.WriteLine("End: " + DateTime.Now.Millisecond);
    }

    // Empty 50x50 JPG with text written in the center
    private byte[] GetDummyImage(string text)
    {
        using (var bmp = new Bitmap(50, 50))
        using (var gr = Graphics.FromImage(bmp))
        {
            gr.Clear(Color.White);
            gr.DrawString(text,
                new Font(FontFamily.GenericSansSerif, 10, FontStyle.Regular, GraphicsUnit.Point),
                Brushes.Black, new RectangleF(0, 0, 50, 50),
                new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
            using (var stream = new MemoryStream())
            {
                bmp.Save(stream, ImageFormat.Jpeg);
                return stream.ToArray();
            }
        }
    }
}

Тестовые прогоны

  • Прогон 1, без изменений: страница загружается быстро, в окне вывода отображается случайное сочетание Start а также Ends, что означает, что запросы обрабатываются параллельно.

  • Запустить 2, добавить пустой Session_Start в global.asax (нужно нажать F5 один раз в браузере, не знаю, почему это так): Start а также End альтернативный, показывающий, что запросы обрабатываются последовательно. Многократное обновление браузера показывает, что это приводит к проблемам с производительностью, даже если отладчик не подключен.

  • Run 3, как Run 2, но добавить EnableSessionState="ReadOnly" к @Page директива GetImage.aspx: Вывод отладки показывает несколько Startс до первого End, Мы снова параллельны, и у нас хорошие показатели.


1 Да, я знаю, что это следует делать с помощью обработчика ashx. Это просто пример.

Не могу сказать вам, что делает ваш отладчик (intellitrace? Подробное ведение журнала? Исключения первого шанса?), Но у вас все еще есть возможность сеансов обрабатывать параллельные запросы.

Доступ к состоянию сеанса ASP.NET является эксклюзивным для каждого сеанса, что означает, что если два разных пользователя делают параллельные запросы, доступ к каждому отдельному сеансу предоставляется одновременно. Однако, если два одновременных запроса сделаны для одного и того же сеанса (с использованием одного и того же значения SessionID), первый запрос получает эксклюзивный доступ к информации сеанса. Второй запрос выполняется только после того, как первый запрос завершен. (Второй сеанс также может получить доступ, если исключительная блокировка информации освобождается, потому что первый запрос превышает время ожидания блокировки.) Если значение EnableSessionState в директиве @ Page установлено в ReadOnly, запрос только для чтения Информация о сеансе не приводит к исключительной блокировке данных сеанса. Однако запросы только для чтения для данных сеанса, возможно, все еще должны ждать блокировки, установленной запросом чтения-записи, для очистки данных сеанса.

Источник: ASP.NET Session State Overview, мой акцент

Другие вопросы по тегам