我刚刚发现,ASP.Net Web应用程序中的每个请求在请求开始时都会获得一个Session锁,然后在请求结束时释放它!
如果这对你造成影响,就像我一开始对你而言,这基本上意味着以下几点:
-
任何时候ASP.Net网页需要很长时间才能加载(可能是由于数据库调用速度慢等),并且用户决定要导航到另一个页面,因为他们厌倦了等待,他们不能! ASP.Net会话锁强制新页面请求等待,直到原始请求完成其缓慢的负载 . Arrrgh .
-
任何时候UpdatePanel加载缓慢,并且用户决定在UpdatePanel完成更新之前导航到另一个页面......他们不能! ASP.net会话锁强制新页面请求等待原始请求完成其缓慢的负载 . 双Arrrgh!
那有什么选择呢?到目前为止,我想出了:
-
实现ASP.Net支持的自定义SessionStateDataStore . 我没有找到太多的副本,它似乎有点高风险,容易搞砸 .
-
跟踪正在进行的所有请求,如果来自同一用户的请求,则取消原始请求 . 看起来有点极端,但它会起作用(我认为) .
-
不要使用Session!当我需要某种状态供用户使用时,我可以使用Cache代替,以及经过身份验证的用户名上的关键项,或者其他类似的东西 . 再次看起来有点极端 .
我真的不敢相信ASP.Net微软团队会在版本4.0的框架中留下如此巨大的性能瓶颈!我错过了一些明显的东西吗使用ThreadSafe集合进行会话有多难?
8 回答
好的,这么大的道具给Joel Muller所有的投入 . 我的最终解决方案是使用本MSDN文章末尾详细介绍的Custom SessionStateModule:
http://msdn.microsoft.com/en-us/library/system.web.sessionstate.sessionstateutility.aspx
这是:
实施起来非常快(实际上似乎比提供者路线更容易)
使用了很多开箱即用的标准ASP.Net会话处理(通过SessionStateUtility类)
这对我们的应用程序的“快照”感觉产生了巨大的影响 . 我仍然无法相信ASP.Net Session的自定义实现会锁定整个请求的会话 . 这给网站带来了如此巨大的迟缓 . 从我必须做的在线研究数量(以及与几位经验丰富的ASP.Net开发人员的对话)来看,很多人都遇到过这个问题,但是很少有人能够深入了解这个问题 . 也许我会写信给Scott Gu ...
我希望这可以帮助那里的一些人!
我开始使用AngiesList.Redis.RedisSessionStateModule,除了使用(非常快)Redis server存储(我使用的是windows port - 虽然也有MSOpenTech port),它绝对没有锁定会话 .
在我看来,如果您的应用程序以合理的方式构建,这不是问题 . 如果您确实需要锁定的,一致的数据作为会话的一部分,您应该专门实现锁定/并发检查 .
在我看来,MS决定默认锁定每个ASP.NET会话只是为了处理糟糕的应用程序设计,这是一个糟糕的决定 . 特别是因为似乎大多数开发人员都没有/甚至没有意识到会话被锁定,更不用说应用程序显然需要结构化,因此您可以尽可能地执行只读会话状态(尽可能选择退出) .
如果您的页面未修改任何会话变量,则可以选择退出此锁定的大部分内容 .
如果您的页面没有读取任何会话变量,则可以完全退出此锁定页面 .
如果您的所有页面都没有使用会话变量,只需关闭web.config中的会话状态即可 .
我很好奇,如果它不使用锁,你认为“一个ThreadSafe集合”会如何成为线程安全的?
编辑:我应该通过“选择退出大部分锁定”来解释我的意思 . 可以同时为给定会话处理任意数量的只读会话或无会话页面,而不会相互阻塞 . 但是,读写会话页面在完成所有只读请求之前无法开始处理,并且在运行时,它必须具有对该用户会话的独占访问权限才能保持一致性 . 锁定单个值是行不通的,因为如果一个页面将一组相关值更改为一组,该怎么办?您如何确保同时运行的其他页面能够获得用户会话变量的一致视图?
如果可能的话,我建议你尽量减少会话变量设置后的修改 . 这将允许您使大多数页面成为只读会话页面,从而增加了来自同一用户的多个同时请求不会相互阻塞的可能性 .
我根据这个帖子中发布的链接准备了一个库 . 它使用MSDN和CodeProject中的示例 . 感谢James .
我也做了Joel Mueller建议的修改 .
代码在这里:
https://github.com/dermeister0/LockFreeSessionState
HashTable module:
ScaleOut StateServer module:
Custom module:
如果要实现对Memcached或Redis的支持,请安装此软件包 . 然后继承 LockFreeSessionStateModule 类并实现抽象方法 .
一些使用Redis的无锁会话提供程序:
https://github.com/angieslist/AL-Redis(gregmac在此主题中的建议 . )
https://github.com/welegan/RedisSessionProvider(NuGet:RedisSessionProvider)
https://github.com/efaruk/playground/tree/master/UnlockedStateProvider(NuGet:UnlockedStateProvider.Redis)
除非您的应用程序有特殊需求,否则我认为您有两种方法:
根本不使用会话
按原样使用会话,并按照joel提到的那样执行微调 .
会话不仅是线程安全的,而且是状态安全的,您知道在当前请求完成之前,每个会话变量都不会从另一个活动请求更改 . 为了实现这一点,您必须 ensure 该会话 WILL BE LOCKED 直到当前请求完成 .
您可以通过多种方式创建类似行为的会话,但如果它不锁定当前会话,则不会是“会话” .
对于你提到的具体问题,我认为你应该检查 HttpContext.Current.Response.IsClientConnected . 这对于防止不必要的执行和在客户端上等待是有用的,尽管它不能完全解决这个问题,因为这只能通过池方式而不是异步方式使用 .
对于ASPNET MVC,我们做了以下事情:
默认情况下,通过覆盖
DefaultControllerFactory
在所有控制器的操作上设置SessionStateBehavior.ReadOnly
在需要写入会话状态的控制器操作上,使用属性标记以将其设置为
SessionStateBehaviour.Required
创建自定义ControllerFactory并覆盖
GetControllerSessionBehaviour
.AcquireSessionLockAttribute
在
global.asax.cs
中连接创建的控制器工厂现在,我们可以在单个
Controller
中同时拥有read-only
和read-write
会话状态 .将控制器的会话状态标记为只读或禁用将解决问题 .
您可以使用以下属性修饰控制器以将其标记为只读:
System.Web.SessionState.SessionStateBehavior枚举具有以下值:
默认
已禁用
ReadOnly
必填
只是为了帮助任何有这个问题的人(在同一个会话中执行另一个时锁定请求)...
今天我开始解决这个问题,经过几个小时的研究,我通过从 Global.asax 文件中删除了
Session_Start
方法(即使是空的)来解决它 .这适用于我测试过的所有项目 .