为什么逻辑调用上下文存储的对象必须可序列化而非直接传递引用?
执行上下文序列化设计原因解答
背景示例
以下是某书中的代码示例:
public static void Main() { // 向主线程的逻辑调用上下文存入数据 CallContext.LogicalSetData("Name", "Jeffrey"); // 让线程池线程执行后续任务 // 线程池线程可以访问逻辑调用上下文的数据 ThreadPool.QueueUserWorkItem(state => Console.WriteLine("Name={0}", CallContext.LogicalGetData("Name"))); ... }
作者表述如下:
添加到逻辑调用上下文的条目必须可序列化。流动包含逻辑调用上下文数据条目的执行上下文会大幅损耗性能,因为捕获执行上下文需要对所有数据条目执行序列化和反序列化操作。
如果要向调用上下文传递自定义类实例,代码写法如下:
public static void Main() { // 向主线程的逻辑调用上下文存入数据 CallContext.LogicalSetData("Name", new Person()); // 让线程池线程执行后续任务 // 线程池线程可以访问逻辑调用上下文的数据 ThreadPool.QueueUserWorkItem(state => Console.WriteLine("Name={0}", CallContext.LogicalGetData("Name"))); ... } [Serializable] class Person { ... }
即Person类必须通过Serializable特性标记为可序列化。
问题解答
这个设计是由CallContext和执行上下文的定位决定的,核心原因有两个:
- 首先,
CallContext设计之初就需要支持跨应用域、跨进程甚至跨机器的远程调用场景,比如.NET早期的Remoting架构。这种场景下两个运行环境的托管堆完全隔离,内存地址没有任何意义,只能通过序列化生成对象副本才能跨边界传递,要求可序列化是为了兼容这类跨边界流动的需求。 - 其次,逻辑调用上下文的设计预期是不同执行流的上下文数据相互隔离,如果直接传递堆引用,子线程修改引用指向的对象时,会直接修改父线程上下文的数据,破坏隔离性,导致不可预期的线程安全问题。序列化后生成的独立副本可以保证不同执行流的上下文数据互不干扰。
你提到的同进程内直接传引用的优化方案,在.NET Core及之后的版本已经实现:新的AsyncLocal<T>替代了老旧的CallContext,专门适配同进程内异步上下文流动的场景,不需要序列化,性能提升非常明显。
内容的提问来源于stack exchange,提问作者user9623401
相关产品推荐
相关产品推荐

