• 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(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

  • 相关阅读:
    JAVA-JDBC
    如何优雅地拼SQL的in子句
    Groovy 语言尝鲜
    小而美的CNC机器
    GCode软件和资料
    基于PC的运动控制
    CAD/CAM软件
    工控硬件
    数控系统
    Visual Studio 2019 Community 版离线注册
  • 原文地址:https://www.cnblogs.com/ljzforever/p/3142782.html
Copyright © 2020-2023  润新知