在应用开发中,我们经常需要设置一些上下文(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. mysql修改数据表名

    在使用mysql时,经常遇到表名不符合规范或标准,但是表里已经有大量的数据了,如何保留数据,只更改表名呢? 可以通过建一个相同的表结构的表,把原来的数据导入到新表中,但是这样视乎很麻烦. 能否简单使用 ...

  2. WIN8 MTK驱动不能安装解决办法

    1.把鼠标移动到桌面最右下角的位置会出来一个侧边栏,按那个齿轮就是“设置”,会出来个菜单,选择最下边的“更多电脑设置” 注:也可以按快捷键“WIN+I” 2.选择“常规”→“高级启动”→”立即重启“ ...

  3. php 的包管理工具 composer

    官方网站 https://getcomposer.org/ 下载地址 https://getcomposer.org/download/ 安装教程 https://laravist.com/serie ...

  4. Centos7 创建个文件 thread 怪现象

    我在~下创建个目录thread, 然后写了个程序, 再执行, 提示需要权限, 之后我重命名下文件的名字, 重新编译下, 就可以正常执行了.

  5. JSTL标签使用说明

    使用jstl需进行以下操作 a.下载jstl. b.解压jar文件将jstl.jar和standard.jar文件放到项目lib文件夹. c.在需要使用jstl地方引用标签库,比如在jsp页面引用以下 ...

  6. java notify和notifyAll的区别

    首先从名字可以了解,notify是通知一个线程获取锁,notifyAll是通知所有相关的线程去竞争锁. notify不能保证获得锁的线程,真正需要锁,并且可能产生死锁. 举例1: 所有人(消费者线程) ...

  7. rabbitmq之work_pool

    worker_pool_worker的作用是用来完成数据操作. 如何获取worker是从worker_pool里获取,并由worker_pool管理. 起动时间: -rabbit_boot_step( ...

  8. linux下tar、zip等压缩、解压命令

    .tar解包:tar xvf FileName.tar打包:tar cvf FileName.tar DirName(注:tar是打包,不是压缩!)-------------------------- ...

  9. 实用命令dd

    1.命令简介 dd 的主要选项: 指定数字的地方若以下列字符结尾乘以相应的数字: b=512, c=1, k=1024, w=2, xm=number m if=file #输入文件名,缺省为标准输入 ...

  10. jquery的toFixed方法的正确使用

    最近一段时候公司的项目中遇到这么个事情,需要计算手续费,而这个手续费必须是保留小数点后面两位,且是由小数点后面第三位四舍五入,就这么个场景: 说说我计算的过程,下面是前两个数是测试用的: howMuc ...