• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Asp.Net在多线程环境下的状态存储问题

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

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


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap