asp.net mvc session锁问题 Ajax请求慢问题
一、会话状态Session
解决方法:
不需要写session的control上 加: [SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
需要写Session的control上(比如登入登出) 加: [SessionState(System.Web.SessionState.SessionStateBehavior.Required)]
Session用于服务器端状态管理,使用Session之后,每个客户端都可以将实际的数据保存在服务器上,对于每个客户端的数据,将会生成一个对应的唯一的key(保存在客户端)。客户端与服务器端就是通过这个key来确认客户端的身份,通常这个key为SessionID。
一般情况下,SessionID以Cookie的形式保存在浏览器中,在不使用Cookie的情况下,也可以将这个SessionID嵌入到访问网页的URL中。
二、服务器端Session
在页面对象或者HttpContext对象中,都有一个名为Session的属性,在一次会话中,它们引用的都是同一个对象。
public HttpSessionState Session { get; }
Session对象是HttpSessionState类的实例。Session是保存在服务器端的,对每个登录到网站的用户都有一份,是独有的,而其他用户无法共享。
HttpSessionState来自于HttpModule的SessionStateModule。在每次请求处理过程中,HttpApplication的请求的处理管道中会检查当前请求的处理程序是否实现了接口IRequiresSessionState,如果实现的话,那么SessionStateModule将为这个请求分配HttpSessionState。同时SessionStateModule还负责SessionID的生成、Cookieless会话管理、从外部状态提供程序中检索会话数据以及将数据绑定到请求的调用上下文。
- 对于一般处理程序,默认情况下没有实现IRequiresSessionState接口。所以如果想要在一般处理程序中使用Session,可以通过实现IRequiresSessionState接口来解决这个问题,这个接口是一个标记接口,并没有定义任何内容。
- 对于页面处理程序,可以将页面指令@Page的EnableSessionState属性设置为true,以允许页面可以请求会话状态的写入权限。这是默认的设置。还可以将EnableSessionState属性设置为ReadOnly,此时派生的实际页面类将会实现接口IReadOnlySessionState,在这种情况下,页面可以拥有会话状态的只读权限。
SessionStateModule模块从特定状态提供程序中读取数据。在程序代码中实际上访问的是会话数据在本地内存中的副本,如果其他页面也视图同步访问该会话状态就可能会导致数据冲突。为了避免这种情况,SessionStateModule模块实现了一个读取器/写入器的锁定机制,并对状态值的访问进行排队。对会话状态具有写入权限的页面将保留该会话的写入器锁定,直到请求终止。
如果页面请求设置一个读取器锁定,同一会话中同时处理的其他请求将无法更新会话状态,但是至少可以进行读取。如果页面请求为会话状态设置一个写入锁,那么所有其他页面都被阻止,无论他们是否要读取或写入内容。例如,如果同时有两段程序视图在同一个Session中写入内容,一段程序必须等到另一段程序完成后才能写入。在AJAX程序设计中,必须注意这种情况的发生。
来看一个非常有趣的示例:Asp.net设置session后变单线程执行
新建一个MVC项目,添加一个Controller如下:
public class HomeController : Controller
{
public ActionResult Index()
{
//Session["User"] = "张三"; //特别注意这行代码
return View();
}
public ActionResult Test1()
{
Thread.Sleep(5000);
return Content("长任务Test1完成");
}
public ActionResult Test2()
{
return Content("短任务Test2完成");
}
}
/Home/Index视图代码如下:
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Index</title>
<script src="/Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(function() {
$("#btn1").click(function() {
$.ajax({
url: "/Home/Test1",
dataType: "text",
success: function(response) {
$("#div1").html($("#div1").html() + response + "<br/>");
}
})
$.ajax({
url: "/Home/Test2",
dataType: "text",
success: function(response) {
$("#div1").html($("#div1").html() + response + "<br/>");
}
})
})
})
</script>
</head>
<body>
<div id="div1" style="width:200px; height:200px; border:1px solid #000;">
</div>
<input type="button" id="btn1" value="开始" />
</body>
</html>
在刚开始的时候,Index的Session[]那行代码是注释掉的,输出如下:
乍眼一看,这很正常jQuery的AJAX默认是异步执行,那个先执行完就哪个先显示,没问题。
下面,启用那行被注释掉的Session代码,输入如下:
这次点击按钮没有反应,虽然jQuery的AJAX是已经发送出去了,但是Asp.net必须要等到第一个请求执行完毕之后,第二个请求才开始执行。这从google浏览器的请求信息可以看到,两个请求几乎是同时发出去的,是Asp.net使用了Session导致的问题:
特别说明:只有写Session时,Asp.net才会阻塞请求,但是只要你访问过写Session的页面,比如是用Session登录的系统之后的操作(直到Session失效都一直锁定,当然只是SessionID相同的情况)。都会存在这个问题。光读Session不会出现这种情况。
这个问题在开发一些并发的Asp.net功能时需要注意,例如进度条。
在MVC中有一个办法可以解决这个问题,在MVC中,可以为本Controller增加以下特性,但是本Controller都不能修改Session了,只能读取。
[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
对于WebForm来说是在aspx顶部的Page后面加上(仅仅加载那个阻塞页面):
EnableSessionState="ReadOnly"