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

    1,Native SQL ->HQL->EJBQL->QBC(Query By Cretira)->QBE(Query By Example) 此排列是根据可实现功能大小排序.

  2. Java中数据库连接池原理机制的详细讲解以及项目连接数据库采用JDBC常用的几种连接方式

    连接池的基本工作原理 1.基本概念及原理 由上面的分析可以看出,问题的根源就在于对数据库连接资源的低效管理.我们知道,对于共享资源,有一个很著名的设计模式:资源池(Resource Pool).该模式 ...

  3. Go 语言学习

    golang中国 http://www.golangtc.com/ 第三方github学习 https://github.com/Unknwon/go-fundamental-programmingh ...

  4. block大小和分区最大容量单个文件最大容量的关系

    block大小和单个文件最大容量的关系(文章来自鸟哥的Linux私房菜http://vbird.dic.ksu.edu.tw/linux_basic/0230filesystem_1.php#ps2) ...

  5. matlab函数bwareaopen的详解

    matlab函数_连通区域 1. matlab函数bwareaopen──删除小面积对象格式:BW2 = bwareaopen(BW,P,conn)作用:删除二值图像BW中面积小于P的对象,默认情况下 ...

  6. Algorithm | Tree traversal

    There are three types of depth-first traversal: pre-order,in-order, and post-order. For a binary tre ...

  7. 移动端性能优化动态加载JS、CSS

    JS CODE (function() { /** * update: * 1.0 */ var version = "insure 1.1.0"; var Zepto = Zep ...

  8. P1967 货车运输 -60分

    打了一个最大生成树+dfs,60分成功tle #include <bits/stdc++.h> using namespace std; const int maxn = 10005; c ...

  9. 【行业交流】2016 TiD质量竞争力大会——移动互联网测试到质量的转变之路

    ◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/5691233.html TiD质量大会在北京召开,有幸去参加 ...

  10. 回归测试---junit

    回归测试是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误. JUnit是一个Java语言的单元测试框架. http://blog.csdn.net/andycpp/ar ...