zoukankan      html  css  js  c++  java
  • CallContext线程数据缓存-调用上下文

    一、CallContext 概述

    命名空间:System.Runtime.Remoting.Messaging

    CallContext 用于提供与执行代码路径一起传送的属性集,直白讲就是:提供线程(多线程/单线程)代码执行路径中数据传递的能力。

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

    CallContext成员

    • SetData:    存储给定的对象并将其与指定名称关联。
    • GetData:    从CallContext中检索具有指定名称的对象    
    • LogicalSetData:    将给定的对象存储在逻辑调用上下文,并将其与指定名称关联。可用于多线程环境
    • LogicalGetData:     从逻辑调用上下文中检索具有指定名称的对象。可用于多线程环境
    • FreeNamedDataSlot:    清空具有指定名称的数据槽。可用于多线程环境
    • HostContext属性:     获取或设置与当前线程相关联的主机上下文。在Web环境下等于System.Web.HttpContext.Current

    GetData、SetData

    • 只能用于单线程环境,如果发生了线程切换,存储的数据也会随之丢失

    • 可以用于同一线程中的不同地方,传递数据

    LogicalSetData、LogicalGetData

    • LogicalSetData、LogicalGetData可用于在多线程环境下传递数据;
    • LogicalSetData只是存储当前线程以及子线程的数据槽
    • LogicalGetData获取的是当前线程或父线程的数据槽对象,拿到的是对象的引用
    • FreeNamedDataSlot清除当前线程,之前已经运行子任务,不受影响,不能清除子线程的数据槽;

    二、 CallContext不跨线程传播的方法:GetData、SetData

    可以利用CallContext 实现单例,默认情况下,CallContext 的数据不跨线程传播。

    1、在处理多组件共用Context时非常有用,比如常见的EF 可以将实例的DBEntity存储在其中,可以一次访问只实例化一次,便于管理且不用多次实例访问对象

    public static class DbContextHelper
    {
        private static DbContext context = null;
        private const string SessionKey_DbContext = "Entities";
        public static DbContext GetDbContext()
        {
            if (CallContext.GetData(SessionKey_DbContext) == null)
            {
                CallContext.SetData(SessionKey_DbContext, new Entities());
            }
            return CallContext.GetData(SessionKey_DbContext) as Entities;
        }
    }

    2、类单例

    void Main()
    {
        MyAppContext.Current.FirstName = "a";
        Console.Write(MyAppContext.Current.FirstName);
    }
    
    public class MyAppContext
    {
        const string contextKey = "MyAppContext:ContextKey";
        public string FirstName { get; set; }
        public static MyAppContext Current
        {
            get
            {
                if (CallContext.GetData(contextKey) == null)
                {
                    CallContext.SetData(contextKey, new MyAppContext());
                }
                return CallContext.GetData(SessionKey_DbContext) as MyAppContext;
            }
        }
    }

    三、 CallContext跨线程传播的方法:ILogicalSetData、LogicalGetData

    要让CallContext实现跨线程传播,可以调用CallContext的静态方法ILogicalSetData,或让上下文类实现ILogicalThreadAffinative 接口。

    线程本地存储

    线程池可能不会释放使用过的线程,导致多次执行之间可能共享数据(可以每次执行前重置线程本地存储的数据)。

    for (var i = 0; i < 10; i++)
    {
        Thread.Sleep(10);
    
        Task.Run(() =>
        {
            var slot = Thread.GetNamedDataSlot("test");
            if (slot == null)
            {
                Thread.AllocateNamedDataSlot("test");
            }
    
            if (Thread.GetData(slot) == null)
            {
                Thread.SetData(slot, DateTime.Now.Millisecond);
            }
    
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + Thread.GetData(slot));
        });
    }

    结果

    调用上下文

    每次执行的数据是完全隔离的,非常符合我们的期望。但是,如果我们期望调用期间又开启了一个子线程,如何让子线程访问父线程的数据呢?这就需要使用到:“逻辑调用上下文”。

    Console.WriteLine("测试:CallContext.SetData");
    for (var i = 0; i < 10; i++)
    {
        Thread.Sleep(10);
    
        Task.Run(() =>
        {
            if (CallContext.GetData("test") == null)
            {
                CallContext.SetData("test", DateTime.Now.Millisecond);
            }
    
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.GetData("test"));
        });
    }

    结果

    每次执行的数据是完全隔离的,非常符合我们的期望。

    逻辑调用上下文

    如果我们期望调用期间又开启了一个子线程,如何让子线程访问父线程的数据呢?这就需要使用到:“逻辑调用上下文”。

    注意 ExecutionContext.SuppressFlow(); 和ExecutionContext.RestoreFlow();它们分别能阻止传播和重置传播,默认是允许传播的。

    Console.WriteLine("测试:CallContext.SetData");
    Task.Run(() =>
    {
        CallContext.SetData("test", "段光伟");
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.GetData("test"));
    
        Task.Run(() =>
        {
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.GetData("test"));
        });
    });
    
    Thread.Sleep(100);
    
    Console.WriteLine("测试:CallContext.LogicalSetData");
    Task.Run(() =>
    {
        CallContext.LogicalSetData("test", "段光伟");
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.LogicalGetData("test"));
    
        Task.Run(() =>
        {
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.LogicalGetData("test"));
        });
    
        ExecutionContext.SuppressFlow();
        Task.Run(() =>
        {
            Console.WriteLine("SuppressFlow 之后:" + CallContext.LogicalGetData("test"));
        });
    
        ExecutionContext.RestoreFlow();
        Task.Run(() =>
        {
            Console.WriteLine("RestoreFlow 之后:" + CallContext.LogicalGetData("test"));
        });
    });

    输出

    四、Web中的CallContext

    HttpContext.Current(包括Session)的存储是基于当前线程的CallContext,在非请求处理线程(即其他线程)是无法获取当前HttpContext的(不跨线程传播)。

  • 相关阅读:
    js判断浏览器是否支持flash的方法
    一个基于原生JavaScript开发的、轻量的验证码生成插件
    自适应宽度元素单行文本省略用法探究
    getBoundingClientRect方法获取元素在页面中的相对位置
    修改表单元素中placeholder属性样式、清除IE浏览器中input元素的清除图标和眼睛图标
    tooltip.css-2.0文档
    理解原型与原型链
    javaScript识别网址文本并转为链接文本
    javaScript回调函数
    函数节流(throttle)与函数去抖(debounce)
  • 原文地址:https://www.cnblogs.com/springsnow/p/9434008.html
Copyright © 2011-2022 走看看