在应用开发中,我们经常需要设置一些上下文(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吧.

欢迎各路朋友指正.

参考

HttpContext.Current并非无处不在

如何实现对上下文(Context)数据的统一管理 [提供源代码下载]

CallContext和多线程

HTTPContext across threads

Do ASP.NET Requests always BeginRequest and EndRequest on the same thread?

CallContext vs ThreadStatic

ThreadStatic, CallContext and HttpContext in ASP.Net

CallContext vs. ThreadStatic vs. HttpContext

关于线程及CallContext

多线程编程之计算限制型异步操作

CallContext.LogicalGetData Vs. CallContext.GetData

Asp.Net在多线程环境下的状态存储问题的更多相关文章

  1. SQLite在多线程环境下的应用

    文一 SQLite的FAQ里面已经专门说明,先贴出来.供以后像我目前的入门者学习. (7) 多个应用程序或者同一个应用程序的多个例程能同时存取同一个数据库文件吗? 多进程可以同时打开同一个数据库,也可 ...

  2. HttpClient在多线程环境下踩坑总结

    问题现场 在多线程环境下使用HttpClient组件对某个HTTP服务发起请求,运行一段时间之后发现客户端主机CPU利用率呈现出下降趋势,而不是一个稳定的状态. 而且,从程序日志中判断有线程处于han ...

  3. 多线程环境下的UI异步操作

    转自原文 多线程环境下的UI异步操作 解决VS中,线程间不可互操作的问题,一揽子解决方案: 一.首先,定义一个类:SetControlProperty using System.Reflection; ...

  4. C#多线程环境下调用 HttpWebRequest 并发连接限制

    C#多线程环境下调用 HttpWebRequest 并发连接限制 .net 的 HttpWebRequest 或者 WebClient 在多线程情况下存在并发连接限制,这个限制在桌面操作系统如 win ...

  5. C++多线程环境下的构造函数

    多线程的环境里,我们总不可避免要使用锁.于是一个常见的场景就是: class ObjectWithLock { private: std::mutex mtx_; SomeResType shared ...

  6. 多线程环境下非安全Dictionary引起的“已添加了具有相同键的项”问题

    问题: 代码是在多线程环境下,做了简单的Key是否存的判断, 测试代码如下: public class Program { static Dictionary<string, Logger> ...

  7. 单例模式在多线程环境下的lazy模式为什么要加两个if(instance==null)

    刚才在看阿寻的博客”C#设计模式学习笔记-单例模式“时,发现了评论里有几个人在问单例模式在多线程环境下为什么lazy模式要加两个if进行判断,评论中的一个哥们剑过不留痕,给他们写了一个demo来告诉他 ...

  8. 一个由单例模式在多线程环境下引发的 bug

    问题症状 HTTP 日志系统,老是出现日志信息覆盖的情况.比如同时调用 A 接口和 B 接口,B 接口请求响应信息变成了 A 接口请求响应相关信息.这个问题在并发量大的情况下越来越严重. 问题初步分析 ...

  9. 你是否听说过 HashMap 在多线程环境下操作可能会导致程序死循环?

    作者:炸鸡可乐 原文出处:www.pzblog.cn 一.问题描述 经常有些面试官会问,是否了解过 HashMap 在多线程环境下使用时可能会发生死循环,导致服务器 cpu 100% 的线上故障? 关 ...

随机推荐

  1. html中a标签做容器的问题

    今天试验了一下a标签当作容器的问题,若a包含的容器中没有a标签的话,a标签是可以被当作成容器使用的,在谷歌浏览器这种浏览器中是可行的,但是在低版本的IE中会有bug出现,就是浏览器在解析的时候会把a标 ...

  2. visual studio 2012 的制作ActiveX、打包和发布[转]

    开发环境是Vs 2012  Framework 4.0 源地址:http://www.cnblogs.com/li-peng/p/3455247.html 源码和制作工具在文章最下边 一. Activ ...

  3. Javascript 中的一些关于时间的操作【转】

    1.时间对象和一些简单操作函数 var myDate = new Date(); myDate.getYear();       //获取当前年份(2位) myDate.getFullYear(); ...

  4. 第 6 章 贴近servlet

    服务器在获得请求的时候会先根据jsp页面生成一个java文件,然后使用jdk的编译器将此文件编译,最后运行得到的class文件处理用户的请求返回响应.如果再有请求访问这jsp页面,服务器会先检查jsp ...

  5. HashMap实现原理分析

    1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端. 数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大.但数组的二分查找时间复杂度小,为O(1 ...

  6. C++ 画星号图形——空心正方形(核心代码)

    //输出m行m列的空心正方矩形. int m;//m用来控制行列数. cout<<"请输入要打印的行列数n(n>=4):\n"; cin>>m; ;i ...

  7. Unity3D 开发 之 JDK安装与环境变量配置

     安装JDK 选择安装目录 安装过程中会出现两次 安装提示 .第一次是安装 jdk ,第二次是安装 jre .建议两个都安装在同一个java文件夹中的不同文件夹中.(不能都安装在java文件夹的根目录 ...

  8. C#的TreeView标记

    今天用到了TreeView控件,多次添加后发现内容是重复的,于是用到清除:this.myTreeView.Nodes.Clear(): 如果想在添加完节点后,默认全展开:this.myTreeView ...

  9. smaller programs should improve performance

    COMPUTER ORGANIZATION AND ARCHITECTURE DESIGNING FOR PERFORMANCE NINTH EDITION In this section, we l ...

  10. JDBC操作

    String sql = "SELECT * from lib where name = ?";List<Lib> list = getJdbcTemplate().q ...