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% 的线上故障? 关 ...
随机推荐
- win10添加打印机--无法访问指定设备,路径或文件。。
win10添加打印机无法访问指定设备,路径或文件..后来发现很多按钮点击多说无法访问指定设备,路径或文件.. 解决添加打印机问题: 在搜索栏中搜索:print (从这里添加) 彻底解决: 添加环境变量 ...
- js中常用的Tab切换
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- spring security为不同用户显示各自的登录成功页面
一个常见的需求是,普通用户登录之后显示普通用户的工作台,管理员登陆之后显示后台管理页面.这个功能可以使用taglib解决. 其实只要在登录成功后的jsp页面中使用taglib判断当前用户拥有的权限进行 ...
- vbox下Oracle Enterprise liunx5.4虚拟机安装10G RAC实验(三)
接第二篇 http://www.cnblogs.com/myrunning/p/3996183.html 4.安装集群软件 4.1验证安装环境 经过检查发现以下3个包检查未通过: compat-gcc ...
- C# 截取字符串
1.根据单个分隔字符用split截取 例如 string st="GT123_1"; string[] sArray=st.split("_"); 即可得到sA ...
- 修改cms 管理栏目路径
Foosun.SQLServerDAL.Pagination throw new Exception("没有找到SQL");
- php吧字符串直接转换成数组处理
$str ='123456'; echo strlen($str) - 1; echo '<br>'; echo $str{strlen($str) - 1}; echo '<br& ...
- 1920.154s 0.309s 30817
MyISAM HASH restart-buffer ; ; ; SELECT * FROM grab_sales_rank_month; ; 受影响的行: 时间: .002s [SQL] ; 受影 ...
- ArcGIS Server,4000端口被占用
server使用的端口:http://resources.arcgis.com/zh-cn/help/main/10.2/index.html#//015400000537000000 cmd 输入命 ...
- Day3~Day7(2016/1/23~2016/1/27)
活动的生命周期:onCreate();onStart();onResume();onPause();onStop();onDestroy();onRestart(); 活动的启动模式:standard ...