Asp.Net在多线程环境下的状态存储问题
在应用开发中,我们经常需要设置一些上下文(Context)信息,这些上下文信息一般基于当前的会话(Session),比如当前登录用户的个人信息;或者基于当前方法调用栈,比如在同一个调用中涉及的多个层次之间数据。
在.Net中,常用的有以下三种方法来实现这个特性.
HttpContext.Current.Session或HttpContext.Currnet.Items是大家使用的最多的方式.
[ThreadStatic]方式可以存储单个线程的共享状态.
System.Runtime.Remoting.Messaging.CallContext类则可以存储一个逻辑线程的共享状态,即主线程和其所有子线程都共享这段内存.
在Asp.Net中通常使用第一种方式.但是鱼李写了一篇文章,指出HttpContext.Current并非无处不在,只有是由请求发起的线程,HttpContext.Current才不为空.换句话说,在多线程环境下, 比如是由定时器发起的线程,Currnet属性就为空,这时依赖于它的相关功能便无法完成.比如使用它获取文件路径等.
鱼李给出了两种解决方法,将HttpContext.Current保存在外部变量中,或者通过函数参数传递.
我个人认为这只是折中的解决办法,它没有在解决问题的同时保待简单性,即程序员不需关心HttpContext.Current的保存位置与传递方式,而直接便可使用.
后台又查到了A大的一篇文章.给出了保存下文(Context)信息的通用解决方案,简单来说,在桌面环境使用System.Runtime.Remoting.Messaging.CallContext类,在Web环境下使用常规的HttpContext.Current,但是同时在System.Runtime.Remoting.Messaging.CallContext类中也保存了一个对它的引用.在线程切换时,依照.Net的设计,System.Runtime.Remoting.Messaging.CallContext类中保存的实现了ILogicalThreadAffinative接口的数据都会自动被复制到新的线程中,即完成了上下文传递.
A大给出了一个精妙的解决方案,但却没有解决我所有的疑问,比如48L的园友就提出了"请教一下,callcontxt无论是那种应用都可以使用,为什么还要使用HttpSessionState?".于是我继续探究.终于在一篇老外的博文中找到了答案.
在那篇文章里,老外做了一个试验.有两个页面,均在其构造函数与Page_Load中使用上面三种方式记录当前线程Id,但是在名为slow的页面中人为让处理线程睡一下来模拟耗时操作.首先访问slow页面,在其返回前快速多次刷新fast页面.最终的打印结果让作者surprise了一下.对于slow页面,执行构造函数的线程与Page_Load的线程保持一致,三种方式的记录结果也没有丢失,但是在fast页面中,有可能出现执行构造函数的线程与Page_Load的线程不一致,三种方式的记录结果也丢失了两种:仅剩下HttpContext了.
我的重现代码如下,增加了LogicalSetData方式,后文再表:
public partial class Fast : System.Web.UI.Page
{
[ThreadStatic]
private static string info = string.Empty; public Fast()
{
info = "fast ctor:" + Thread.CurrentThread.ManagedThreadId;
CallContext.SetData("id", Thread.CurrentThread.ManagedThreadId);
CallContext.LogicalSetData("id1", Thread.CurrentThread.ManagedThreadId);
Items["id2"] = Thread.CurrentThread.ManagedThreadId;
} protected void Page_Load(object sender, EventArgs e)
{
Response.Write("ThreadStatic:" + info);
info = string.Empty;
Response.Write("<br />");
Response.Write("CallContext.SetData:" + CallContext.GetData("id"));
Response.Write("<br />");
Response.Write("CallContext.LogicalSetData:" + CallContext.LogicalGetData("id1"));
Response.Write("<br />");
Response.Write("Items:" + Items["id2"]);
Response.Write("<br />");
Response.Write("<br />fast page_load:" + Thread.CurrentThread.ManagedThreadId);
}
} public partial class Slow : System.Web.UI.Page
{
[ThreadStatic]
private static string info = string.Empty; public Slow()
{
Thread.Sleep();
info = "slow ctor:" + Thread.CurrentThread.ManagedThreadId;
CallContext.SetData("id", Thread.CurrentThread.ManagedThreadId);
CallContext.LogicalSetData("id1", Thread.CurrentThread.ManagedThreadId);
Items["id2"] = Thread.CurrentThread.ManagedThreadId;
} protected void Page_Load(object sender, EventArgs e)
{
Thread.Sleep();
Response.Write("ThreadStatic:" + info);
info = string.Empty;
Response.Write("<br />");
Response.Write("CallContext.SetData:" + CallContext.GetData("id"));
Response.Write("<br />");
Response.Write("CallContext.LogicalSetData:" + CallContext.LogicalGetData("id1"));
Response.Write("<br />");
Response.Write("Items:" + Items["id2"]);
Response.Write("<br />");
Response.Write("<br />slow page_load:" + Thread.CurrentThread.ManagedThreadId);
}
}
执行结果如下:


从博文中获知,这是完全正常的执行结果, Asp.Net开发团队还为其起了个好听的名字:Thread-Agile.这个特性表明,即使你使用常规的Asp.Net开发方式,也不能保证所有的代码一定会在同一线程中执行.从其它的文章中获知,这是与负载有关的.负载越大,越有可能产生多线程.每当程序进入一个新的线程中执行时,Asp.Net会手动(是使用额外代码实现的,不是.Net自带的机制)将HttpContext对象复制到新线程中.一方面这能将多线程完全透明,让程序员使用单线程的编程方式编写多线程程序;另一方面CallContext.SetData与[ThreadStatic]就丢失了.但由于使用LogicalSetData方法存储的数据其内部都会自动封装成实现了ILogicalThreadAffinative接口的对象,所以在线程切换时能正常流转.
所以,在Asp.Net环境下,除非自己建立一套上下文环境解决方案,否则在该用的情况下还是老老实实使用HttpContext吧.
欢迎各路朋友指正.
参考
如何实现对上下文(Context)数据的统一管理 [提供源代码下载]
Do ASP.NET Requests always BeginRequest and EndRequest on the same thread?
ThreadStatic, CallContext and HttpContext in ASP.Net
CallContext vs. ThreadStatic vs. HttpContext
CallContext.LogicalGetData Vs. CallContext.GetData
Asp.Net在多线程环境下的状态存储问题的更多相关文章
- SQLite在多线程环境下的应用
文一 SQLite的FAQ里面已经专门说明,先贴出来.供以后像我目前的入门者学习. (7) 多个应用程序或者同一个应用程序的多个例程能同时存取同一个数据库文件吗? 多进程可以同时打开同一个数据库,也可 ...
- HttpClient在多线程环境下踩坑总结
问题现场 在多线程环境下使用HttpClient组件对某个HTTP服务发起请求,运行一段时间之后发现客户端主机CPU利用率呈现出下降趋势,而不是一个稳定的状态. 而且,从程序日志中判断有线程处于han ...
- 多线程环境下的UI异步操作
转自原文 多线程环境下的UI异步操作 解决VS中,线程间不可互操作的问题,一揽子解决方案: 一.首先,定义一个类:SetControlProperty using System.Reflection; ...
- C#多线程环境下调用 HttpWebRequest 并发连接限制
C#多线程环境下调用 HttpWebRequest 并发连接限制 .net 的 HttpWebRequest 或者 WebClient 在多线程情况下存在并发连接限制,这个限制在桌面操作系统如 win ...
- C++多线程环境下的构造函数
多线程的环境里,我们总不可避免要使用锁.于是一个常见的场景就是: class ObjectWithLock { private: std::mutex mtx_; SomeResType shared ...
- 多线程环境下非安全Dictionary引起的“已添加了具有相同键的项”问题
问题: 代码是在多线程环境下,做了简单的Key是否存的判断, 测试代码如下: public class Program { static Dictionary<string, Logger> ...
- 单例模式在多线程环境下的lazy模式为什么要加两个if(instance==null)
刚才在看阿寻的博客”C#设计模式学习笔记-单例模式“时,发现了评论里有几个人在问单例模式在多线程环境下为什么lazy模式要加两个if进行判断,评论中的一个哥们剑过不留痕,给他们写了一个demo来告诉他 ...
- 一个由单例模式在多线程环境下引发的 bug
问题症状 HTTP 日志系统,老是出现日志信息覆盖的情况.比如同时调用 A 接口和 B 接口,B 接口请求响应信息变成了 A 接口请求响应相关信息.这个问题在并发量大的情况下越来越严重. 问题初步分析 ...
- 你是否听说过 HashMap 在多线程环境下操作可能会导致程序死循环?
作者:炸鸡可乐 原文出处:www.pzblog.cn 一.问题描述 经常有些面试官会问,是否了解过 HashMap 在多线程环境下使用时可能会发生死循环,导致服务器 cpu 100% 的线上故障? 关 ...
随机推荐
- SQL servcer 时间日期函数、数据类型转换
1.时间日期函数 2.数据类型转换 3.习题 建立两个表,一个部门表,一个人员表.部门:部门的编号,部门的名称,部门的职责.人员:人员的编号,姓名,年龄,性别,cid所属部门
- 移动端设备UA检测
网上找到的都不全,不是漏这个就是漏那个,有的甚至还把桌面的chrome判断为移动浏览器. 于是自己动手整理,这回算是比较全了.以后发现漏掉的立马加上. if(/AppleWebKit.*Mobile/ ...
- chrome 优秀的插件推荐
就本人使用过的chrome插件推荐下: 1:Adblock Plus 免费的广告拦截器,可阻止所有烦人的广告及恶意软件和跟踪. 2:ChaZD 英文翻译,妈妈再也不用担心我英文看不懂了,ChaZD 查 ...
- java项目开发的一些准备工作
做项目有一段时间了,每次接手一个新项目都要在开发前做些准备工作,方便开发. 有些东西在配置的时候经常会忘记,所有整理一份,方便以后查阅! 1.安装JDK及搭建环境,安装tomcat及搭建环境,这些一般 ...
- PHP第三方登录 -- 微博登录
进化史 博客园 首页 新随笔 联系 管理 订阅 随笔- 9 文章- 0 评论- 0 php 实现qq第三方登录 学习之前,请大家先看一下oAuth协议. 首先呢,我们进入QQ互联的官方网站 ht ...
- git总是出现untracked content怎么解决?
git rm --cached vendor/plugins/open_flash_chart_2 rm -rf vendor/plugins/open_flash_chart_2/.git # BA ...
- Java 内存区域划分
JVM的内存区域划分 学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆.栈以及静态数据区.那么在Java语言当中,内存又是如何划分的 ...
- Gradle--ubuntu
在Ubuntu安装Gradle也是很简单.切记请勿使用apt-get安装Gradle.因为Ubuntu源的Gradle实在太旧.我用的搜狐的源,竟然是2011年. 下面是安装步骤: 1.在官网下载最新 ...
- websocket和swoole
<head></head><body><script type="text/javascript">var sock = null; ...
- nginx yii2环境配置
#user nobody;worker_processes 2;#worker_cpu_affinity 0001 0010 0100 1000 #error_log logs/error.log;# ...