为啥 Global.asax.cs 中的 Session_Start 会导致性能问题?

Posted

技术标签:

【中文标题】为啥 Global.asax.cs 中的 Session_Start 会导致性能问题?【英文标题】:Why does Session_Start in Global.asax.cs cause performance problems?为什么 Global.asax.cs 中的 Session_Start 会导致性能问题? 【发布时间】:2011-05-26 00:08:36 【问题描述】:

当我在 Global.asax.cs 中创建一个空的 Session_Start 处理程序时,它会在向浏览器呈现页面时造成重大影响。

如何重现:

创建一个空的 ASP.NET MVC 3 Web 应用程序(我使用的是 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 个 IFRAME,每个 IFRAME 中都有一个数字。我在这里所做的只是创建一个在幕后加载 20 多个页面的页面。在继续之前,请注意这 20 个页面的加载速度(刷新页面几次以重复加载)。

接下来去你的 Global.asax.cs 并添加这个方法(是的,方法体是空的):

protected void Session_Start()


现在再次运行该页面。这一次您会注意到 20 个 IFRAME 的加载速度要慢得多,一个接一个,相隔大约 1 秒。这很奇怪,因为我们实际上并没有在 Session_Start 中做任何事情……它只是一个空方法。但这似乎足以导致所有后续页面的速度变慢。

有人知道为什么会这样吗?更好的是有人有修复/解决方法吗?

更新

我发现此行为仅在附加调试器时发生(使用 F5 运行)。如果您在没有附加调试器的情况下运行它(Ctrl-F5),那么它似乎没问题。所以,也许这不是一个严重的问题,但它仍然很奇怪。

【问题讨论】:

我在 SPA 应用程序中的 AJAX 请求上遇到了这个问题。引导我远离使用 SessionState。 【参考方案1】:

tl;dr:如果您在使用 Web 表单时遇到此问题,并且不需要对该特定页面中会话状态的写入权限,则将 EnableSessionState="ReadOnly" 添加到您的 @Page 指令会有所帮助。


显然,仅Session_Start 的存在就迫使 ASP.NET 依次执行源自同一 Session 的所有请求。但是,如果您不需要对会话的写入权限,则可以逐页修复此问题(见下文)。

我使用 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、usingnamespace 已跳过):

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();
            
        
    

测试运行

Run 1,未修改:页面加载速度很快,输出窗口显示StartEnds 的随机混合,这意味着请求得到并行处理。

运行2,将空Session_Start添加到global.asax(需要在浏览器中按F5一次,不知道这是为什么):Start和@987654334 @alternate,表明请求是按顺序处理的。多次刷新浏览器表明即使没有附加调试器也会出现性能问题。

运行 3,与运行 2 类似,但将 EnableSessionState="ReadOnly" 添加到 GetImage.aspx@Page 指令中:调试输出在第一个 @987654339 之前显示多个 Starts @。我们又并行了,性能不错。


1 是的,我知道这应该使用 ashx 处理程序来代替。这只是一个例子。

【讨论】:

有趣...你能测试一下当你在调试器之外运行这些测试时会发生什么(即使用 Ctrl-F5 而不是 F5 启动) @Mike:我做到了。运行 2 比其他运行慢,即使没有调试器也是如此(差异没有调试器那么明显,但仍然很明显)。【参考方案2】:

无法告诉您调试器在做什么(intellitrace?详细日志记录?第一次机会异常?),但您仍然掌握会话处理并发请求的能力。

对 ASP.NET 会话状态的访问是每个会话独占的,这意味着如果两个不同的用户发出并发请求,则同时授予对每个单独会话的访问权限。 但是,如果对同一个会话发出两个并发请求(通过使用相同的 SessionID 值),则第一个请求将获得对会话信息的独占访问权。第二个请求只有在第一个请求完成后才会执行。(如果第一个请求超过了锁定超时,释放了对信息的排他锁,第二个会话也可以获得访问权限。)如果 EnableSessionState 中的值@Page 指令设置为 ReadOnly,对只读会话信息的请求不会导致对会话数据的排他锁。但是,会话数据的只读请求可能仍需要等待会话数据的读写请求设置的锁定清除。

来源:ASP.NET Session State Overview,我的重点

【讨论】:

这很有趣,但我认为它不能解释上述行为。 Case #1 和 Case #2 之间的唯一区别是 Global.asax.cs 中存在一个空的 Session_Start 方法。根据您的参考,这两种情况的行为仍应相同。 显然,Global.asax.cs 中的空 Session_Start 方法足以使 ASP.NET 序列化请求。我在使用 Webforms 时遇到了类似的问题,发现可以通过在为并行页面提供服务的 aspx 页面的 @Page 指令中设置 EnableSessionState="ReadOnly" 来修复它(在我的例子中是图像)。我不知道 MVC 是否存在类似的选项。 @Heinzi 非常有趣!您是否有任何文件/证据表明这种行为?如果是这样,请创建一个新答案,我会将其标记为已接受。 @Mike:我还没有找到这方面的文档,但我已经添加了我的发现作为答案;希望它对某人有用。

以上是关于为啥 Global.asax.cs 中的 Session_Start 会导致性能问题?的主要内容,如果未能解决你的问题,请参考以下文章

MVC4 中 Global.asax.cs 页面中的问题

Global.asax.cs 为 /.aspx 执行子请求时出错。 Server.Transfer

ASP.NET MVC:如何在 Global.asax.cs 中的 Application_Start() 中检测浏览器宽度

Global.asax.cs介绍

在 global.asax.cs 中分配后未填充 Insights InstrumentationKey

MVC 应用程序 - 从 Global.asax.cs 检索 LoaderExceptions