CallContext和多线程

前一段时间正好要在某个网页程序上开一个多线程调用多个组件的尝试,这些组件是有其他团队开发的(如:印度/俄罗斯),所以修改它们的代码看起来是不太现实的,但是,令人恼火的是他们的代码中大量的用到了AppContext.Current这个对象(实际上是用了HttpContext.Current.Item来存储的),而一旦异步,HttpContext.Current就不复存在,自然就会不停的报出空引用异常,看起来异步是不太现实的了。

  就在无计可施的时候,突然发现有一个叫CallContext的奇怪的类,藏匿在System.Runtime.Remoting.Messaging这个几乎没人用的namespace下面,当然一开始我仅仅是被它的名称所吸引,直译过来不就是调用上下文吗?感觉这个东西能有点作用。于是查阅了msdn,描述如下:

提供与执行代码路径一起传送的属性集。无法继承此类。

  一句废话。。。看备注吧:

CallContext 是类似于方法调用的线程本地存储区的专用集合对象,并提供对每个逻辑执行线程都唯一的数据槽。数据槽不在其他逻辑线程上的调用上下文之间共享。当 CallContext 沿执行代码路径往返传播并且由该路径中的各个对象检查时,可将对象添加到其中。

当对另一个 AppDomain 中的对象进行远程方法调用时,CallContext 类将生成一个与该远程调用一起传播的 LogicalCallContext 实例。只有公开 ILogicalThreadAffinative 接口并存储在CallContext 中的对象被在 LogicalCallContext 中传播到 AppDomain 外部。不支持此接口的对象不在 LogicalCallContext 实例中与远程方法调用一起传输

  似乎有那么点意思,不过逻辑线程的定义似乎有那么点模棱两可,不过,也提到了一个接口ILogicalThreadAffinative,再看看这个接口定义了什么,一看成员定义。。。没有成员。。。一个空接口,汗了一把,还是看看msdn上如何描述的吧:

标记可以在 LogicalCallContext 中传播到 AppDomain 外部的对象。

  强调了在remoting中的作用,再看看备注:

当对另一个 AppDomain 中的对象进行远程方法调用时,当前的 CallContext 类生成一个将与该调用一起传播到远程位置的 LogicalCallContext。只有公开 ILogicalThreadAffinative 接口并存储在 CallContext 中的对象被传播到 AppDomain 外部。不支持此接口的对象不在LogicalCallContext 实例中与远程方法调用一起传输。

  也是强调在remoting中的作用,但是可以想象,基本上是CallContext中用了类似is ILogicalThreadAffinative的方式,来区别对待不同的对象,对于符合这个接口的将被放到LogicalCallContext,而不符合的另外处理。

  从文档角度,似乎已经没有什么进展了,这时候,突然想起来一个以前看过的很有趣的类型ExecutionContext,namespace是System.Threading,看起来就是多线程准备的,不过,msdn上的例子就说了如何控制传递权限对象的问题,并没有说到如何传递普通对象。

  一时想到所谓的LogicalCallContext会不会在ExecutionContext中存在哪?查了一下msdn,看到备注:

ExecutionContext 类为与执行的逻辑线程相关的所有信息提供单个容器。这包括安全上下文、调用上下文和同步上下文。

ExecutionContext 类提供的功能让户代码可以在用户定义的异步点之间捕获和传输此上下文。公共语言运行库确保在托管进程内运行库定义的异步点之间一致地传输 ExecutionContext。

执行上下文是 COM 单元的托管等效项。在应用程序域中,每当传输线程时都必须传输整个执行上下文。在由 Thread..::.Start 方法、大多数线程池操作和通过 Windows 消息泵进行的 Windows 窗体线程封送处理所导致的传输过程中,将会出现这种情况。在不安全的线程池操作(如UnsafeQueueUserWorkItem 方法)中不会出现这种情况,原因是不安全的线程池操作不会传输压缩堆栈。每当压缩堆栈流动时,托管的主体、同步、区域设置和用户上下文也随之流动。ExecutionContext 类提供 Capture 和 CreateCopy 方法以获取执行上下文,并提供 Run 方法以设置当前线程的执行上下文。

与某个线程相关联的 ExecutionContext 无法在另一个线程上进行设置。尝试这样做会导致引发异常。若要将 ExecutionContext 从一个线程传播到另一个线程,请制作 ExecutionContext 的副本。

ExecutionContext 在内部存储与 LogicalCallContext 相关联的所有数据。这使得可以在复制和传输 ExecutionContext 时传播 LogicalCallContext 数据。

  果然,ExecutionContext中有LogicalCallContext的数据,并且很好的说明了,无论是Thread.Start还是用线程池大多数操作,ExecutionContext都会自动将这些数据传递给那些线程(关于ThreadPool.UnsafeQueueUerWorkItem方法相信用的人应该不多),看起来演员们都到齐了,马上可以演出一场多线程的好戏了。

  首先是起着关键作用ExectionContext,也许我们的代码中没必要出现它的身影,但是那仅仅是因为.net类库的方法,为我们很好封装了这个功能,没有它,想在一个线程中把一个对象告诉另一个线程,就只有通过堆了。

  其次,LogicalCallContext,在众多ExecutionContext传播的对象中,很多是我们无法简单的利用的(总不能为了传播一个对象,去定义一个自定义的权限吧),而LogicalCallContext就是自定义对象的最好的载体。

  最后,剩下的问题就是如何读写这个LogicalCallContext的问题了,也就是终于轮到CallContext出场了。看一下CallContext为我们准备了些什么方法:GetData, SetData, LogicalGetData, LogicalSetData, FreeNamedDataSlot, GetHeader, SetHeader以及HostContext属性。

  第一焦点,当然是GetData和SetData这两个方法,做了个简单的测试,发现这两个方法,确实就是通过ILogicalThreadAffinative接口来决定是否要把对象发给新线程的,而删除这个数据的方法就是FreeNamedDataSlot,现在只要为每一个要在多线程中共用的对象加上一个空接口,并且在多线程开始前在主线程中把对象设置进去,然后在其他线程中再取出来就可以把问题搞定了。多线程部分结束后,不要忘记用FreeNamedDataSlot去删除一下。

  不知道大家注意到没有,出了GetData和SetData外,还有一对LogicalGetData和LogicalSetData,这两个是干什么用的哪?是不是和LogicalCallContext的Logical有什么关系哪?

  又把前面的那个简单的实验做了一下,只不过用了LogicalGetData和LogicalSetData这两个方法,结论是无论是否实现ILogicalThreadAffinative接口,对象都可以在新线程内被访问到,也就是说现在可以传播任何数据给新线程,包括.net定义的string,int等基础类型,这个方案已经接近完美了。

  回过头来看看我的任务吧,只需要修改AppContext.Current属性的实现,并且在多线程开始之前和之后做一个小小的处理,其他的组件就可以原封不动的并发的跑在各自的线程上。

  工作上的事情就到此为止了。

  在来说说CallContext.HostContext属性是干什么的,经过简单的测试,发现在Asp.net程序中,这个HostContext里面放的就是HttpContext的实例,ms也够偷懒的。其实ms只要做一个很小的修改,Asp.net的多线程就不用这么麻烦了,只需要HttpContext实现一下ILogicalThreadAffinative接口,无论新开多少个线程,到那边都能访问到HttpContext.Current了,当然ms没有这么做也是有原因的,一旦HttpContext被传播到其他线程,那么asp.net就很难控制HttpContext对象的生命周期,而HttpContext对象又引用这HttpRequest对象,带来的副作用可能要比想象的大得多。

时间: 2024-10-07 21:03:11

CallContext和多线程的相关文章

框架设计知识点纵览(笔记)

ABP源码分析二:ABP中配置的注册和初始化 ABP源码分析三:ABP Module 1.autofac 统一事件处理方式 上面说的都是为每个类型注册事件,但是如果我们希望为所有类型都注册某一事件,有什么方式来实现呢? (首先申明,OnRelease事件暂时没找到统一注册的方式) 我们可以在builder注册类型前使用RegisterCallback进行统一事件注册,详见代码: var builder = new ContainerBuilder(); builder.RegisterCallb

Abp Uow 设计

初始化入口 在AbpKernelModule类中,通过UnitOfWorkRegistrar.Initialize(IocManager) 方法去初始化 1 /// <summary> 2 /// This class is used to register interceptor for needed classes for Unit Of Work mechanism. 3 /// </summary> 4 internal static class UnitOfWorkReg

CallContext的LogicalCallContext在多线程环境下面公用变量

压根名听说过这个类的看这里:如何实现对上下文(Context)数据的统一管理 原来以为CallContext就可以直接在多线程环境下面共享使用的,今天突然想到:Asp.Net环境下面,设置来设置去的,对象不就被多个客户端搞乱了吗? 随做了以下代码测试: static void Main(string[] args) { CallContext.LogicalSetData("key", "1"); Thread trd = new Thread(new Thread

快速入门系列--CLR--02多线程

最近,由于基础框架的整体升级,因此需要更新所有相关项目的DLL文件.这个过程存在不小的风险,因此也对发布后的生产服务器进行了密切的监控,结果还是出现了个别应用出现异常的情况,很快的占用了大量的服务器内存和CPU等资源.通过研究dump,初步发现是由于配置服务器出现单点故障,然后应用通过多线程调用相关SOA服务时出现异常,引发了ThreadAbortException异常,而且由于原有异常处理代码不够严谨,而且与异步发送报警邮件紧密结合在一起,造成线程数量的几何级增加,最终使得整个服务器不可用.这

ASP.NET多线程下使用HttpContext.Current为null解决方案

多线程或者异步调用中如何访问HttpContext? 前面我还提到在APM模式下的异步完成回调时,访问HttpContext.Current也会返回null,那么此时该怎么办呢? 答案有二种:1. 在类型中添加一个字段来保存HttpContext的引用(异步开始前).2. 将HttpContext赋值给BeginXXX方法的最后一个参数(object state) 建议优先选择第二种方法,因为可以防止以后他人维护时数据成员被意外使用. 引用: 不错的文章:http://www.cnblogs.c

多线程共享变量和 AsyncLocal

>>返回<C# 并发编程> 1. 简介 2. 异步下的共享变量 3. 解析 AsyncLocal 3.1. IAsyncLocalValueMap 的实现 3.2. 结论 1. 简介 普通共享变量: 在某个类上用静态属性的方式即可. 多线程共享变量 希望能将这个变量的共享范围缩小到单个线程内 无关系的B线程无法访问到A线程的值: [ThreadStatic]特性.ThreadLocal<T> .CallContext .AsyncLocal<T> 都具备这个

Java多线程学习(吐血超详细总结)

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 目录(?)[-] 一扩展javalangThread类 二实现javalangRunnable接口 三Thread和Runnable的区别 四线程状态转换 五线程调度 六常用函数说明 使用方式 为什么要用join方法 七常见线程名词解释 八线程同步 九线程数据传递 本文主要讲了java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的一些线程函数用法.概述等. 首先讲一下进程和线程

Spring多线程

Spring是通过TaskExecutor任务执行器来实现多线程和并发编程的.使用ThreadPoolTaskExecutor可实现一个基于线程池的TaskExecutor.而实际开发中任务一般是非阻碍的,即异步的,所以我们要在配置类中通过@EnableAsync开启对异步的支持,并通过在实际执行的Bean的方法中使用@Async注解来声明其是一个异步任务. 实例代码: (1)配置类 package com.lwh.highlight_spring4.ch3.taskexecutor; /**

python进阶学习(一)--多线程编程

1. 多线程 概念:简单地说操作系统可以同时执行多个不用程序.例如:一边用浏览器上网,一边在听音乐,一边在用笔记软件记笔记. 并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务"一起"执行(实际上总有一些任务不在执行,因为切换任务的熟度相当快,看上去一起执行而已) 并行:指的是任务数小于等于CPU核数,即任务真的是一起执行的. 2. 线程 概念:线程是进程的一个实体,是CPU调度和分派的基本单位. threading--单线程执行: 1 import ti