C#使用CallContext缓存线程数据

 更新时间:2022年05月13日 14:53:20   作者:springsnow  
这篇文章介绍了C#使用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的(不跨线程传播)。

到此这篇关于C#使用CallContext缓存线程数据的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • C#使用Datatable导入sqlserver数据库的三种方法

    C#使用Datatable导入sqlserver数据库的三种方法

    本文主要介绍了C#使用Datatable导入sqlserver数据库的三种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-08-08
  • 基于NPOI用C#开发的Excel以及表格设置

    基于NPOI用C#开发的Excel以及表格设置

    这篇文章主要为大家详细介绍了基于NPOI用C#开发的Excel以及表格设置,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • C# 手动/自动保存图片的实例代码

    C# 手动/自动保存图片的实例代码

    C# 手动/自动保存图片的实例代码,需要的朋友可以参考一下
    2013-03-03
  • WPF TextBox和PasswordBox添加水印

    WPF TextBox和PasswordBox添加水印

    这篇文章主要为大家详细介绍了WPF TextBox和PasswordBox添加水印的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-11-11
  • C#发送内置图片html格式邮件的方法

    C#发送内置图片html格式邮件的方法

    这篇文章主要介绍了C#发送内置图片html格式邮件的方法,涉及C#发送邮件的技巧,非常具有实用价值,需要的朋友可以参考下
    2015-04-04
  • 基于C#实现进程回收管理工具

    基于C#实现进程回收管理工具

    这篇文章主要为大家详细介绍了入户基于C#实现一个进程回收管理工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-04-04
  • Unity实现植物识别示例详解

    Unity实现植物识别示例详解

    这篇文章主要介绍了如何通过Unity接入百度AI实现植物识别,接口返回植物的名称,并支持获取识别结果对应的百科信息。感兴趣的可以了解一下
    2022-01-01
  • 从C#程序中调用非受管DLLs的方法

    从C#程序中调用非受管DLLs的方法

    这篇文章主要介绍了从C#程序中调用非受管DLLs的方法,是非常实用的技巧,有助于深入理解Windows程序设计,需要的朋友可以参考下
    2014-10-10
  • C#中的Internal关键字小结

    C#中的Internal关键字小结

    这篇文章主要介绍了C#中的Internal关键字小结的相关资料,需要的朋友可以参考下
    2017-05-05
  • 解析c#显示友好时间的实现代码

    解析c#显示友好时间的实现代码

    本篇文章是对c#中显示友好时间的实现代码进行了介绍,需要的朋友参考下
    2013-05-05

最新评论