以上下文(Context)的形式创建一个共享数据的容器

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器镜像服务 ACR,镜像仓库100个 不限时长
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介:

在很多情况下我们具有这样的需求:为一组相关的操作创建一个执行上下文并提供一个共享的数据容器,而不是简单地定义一个全局变量,或者将数据通过参数传来传去。这样的上下文一般具有其生命周期,它们在目标操作开始执行的时候被激活,在执行完成之后被回收。该上下文一般不能跨越多个线程,以避免多个线程操作相同的数据容器造成数据的不一致。针对这个需求,我们写了一个非常简单的例子,有兴趣的朋友可以看看。[源代码从这里下载]

目录
一、ExecutionContext的基本编程方式
二、异步调用的问题
三、ExecutionContext
四、DependentExecutionContext
五、ExecutionContextScope

一、ExecutionContext的基本编程方式

我将这个作为数据容器的上下文命名为ExecutionContext,我完全借鉴了TransactionScope的编程方式来设计这个ExecutionContext。如下的代码片段体现了ExecutionContext最基本的编程方式:我们通过ExecutionContextScope 来创建当前ExecutionContext,并且控制它的生命周期。当前ExecutionContext通过静态属性Current获取。我们分别调用GetValue和SaveValue进行上下文数据项的获取和设置。

using (ExecutionContextScope contextScope = new ExecutionContextScope())
{
    //Set
    ExecutionContext.Current.SetValue(“ActivityID”, “A001”);
    //Get
    string activityId = ExecutionContext.Current.GetValue<string>(“ActivityID”)
}

和TransactionScope一样,ExecutionContextScope 也支持嵌套。具体来说,当我们采用嵌套的ExecutionContextScope 时,有对应着如下三种不同的上下文共享行为:

  • Required: 外层的ExecutionContext直接被内层使用;
  • RequiresNew:内层创建一个全新的ExecutionContext;
  • Suppress:外层的ExecutionContext在内层中使被屏蔽掉,内层的当前ExecutionContext不存在。

如下的代码片段反映了嵌套使用ExecutionContextScope 的编程方式,上述的三种行为通过作为ExecutionContextScope构造函数参数的ExecutionContextOption枚举来控制。

using (ExecutionContextScope contextScope1 = new ExecutionContextScope())
{
    //...
    using (ExecutionContextScope contextScope2 = new ExecutionContextScope(ExecutionContextOption.))
    {
        //...
    }
    using (ExecutionContextScope contextScope2 = new ExecutionContextScope(ExecutionContextOption.))
    {
        //...
    }
    using (ExecutionContextScope contextScope2 = new ExecutionContextScope(ExecutionContextOption.))
    {
        //...
    }
}

ExecutionContext基本的编程方式,以及三种ExecutionContextScope 嵌套所体现的ExecutionContext创建/共享机制可以通过如下的Unit Test代码来体现:

[TestMethod]
public void SetAndGetContexts1()
{
    string name = Guid.NewGuid().ToString();
    string value1 = Guid.NewGuid().ToString();
    string value2 = Guid.NewGuid().ToString();

    //1. Outside of ApplicationContextScope: ApplicationContext.Current = null
    Assert.IsNull(ExecutionContext.Current);

    //2. Current ApplicationContext is avilable in the ApplicationContextScope.
    using (ExecutionContextScope contextScope = new ExecutionContextScope())
    {
        ExecutionContext.Current.SetValue(name, value1);
        Assert.AreEqual<string>(value1, ExecutionContext.Current.GetValue<string>(name));
    }

    //3. Nested ApplicationContextScope: ApplicationContextOption.Required
    using (ExecutionContextScope contextScope1 = new ExecutionContextScope())
    {
        ExecutionContext.Current.SetValue(name, value1);
        using (ExecutionContextScope contextScope2 = new ExecutionContextScope(ExecutionContextOption.Required))
        {
            Assert.AreEqual<string>(value1, ExecutionContext.Current.GetValue<string>(name));

            ExecutionContext.Current.SetValue(name, value2);
            Assert.AreEqual<string>(value2, ExecutionContext.Current.GetValue<string>(name));
        }
        Assert.AreEqual<string>(value2, ExecutionContext.Current.GetValue<string>(name));
    }


    //4. Nested ApplicationContextScope: ApplicationContextOption.RequiresNew
    using (ExecutionContextScope contextScope1 = new ExecutionContextScope())
    {
        ExecutionContext.Current.SetValue(name, value1);
        using (ExecutionContextScope contextScope2 = new ExecutionContextScope(ExecutionContextOption.RequiresNew))
        {
            Assert.IsNotNull(ExecutionContext.Current);
            Assert.IsNull(ExecutionContext.Current.GetValue<string>(name));
            ExecutionContext.Current.SetValue(name, value2);
            Assert.AreEqual<string>(value2, ExecutionContext.Current.GetValue<string>(name));
        }
        Assert.AreEqual<string>(value1, ExecutionContext.Current.GetValue<string>(name));
    }

    //5. Nested ApplicationContextScope: ApplicationContextOption.Supress
    using (ExecutionContextScope contextScope1 = new ExecutionContextScope())
    {
        ExecutionContext.Current.SetValue(name, value1);
        using (ExecutionContextScope contextScope2 = new ExecutionContextScope(ExecutionContextOption.Suppress))
        {
            Assert.IsNull(ExecutionContext.Current);
        }
        Assert.AreEqual<string>(value1, ExecutionContext.Current.GetValue<string>(name));
    }
}

二、异步调用的问题

如果具有当前ExecutionContext的程序以异步的方式执行相应的操作,我们希望当前操作和异步操作使用不同的数据容器,否则就会出现并发问题;但是我们又希望在异步操作开始执行的时候,当前的上下文数据能够自动地拷贝过去。为此我们依然借鉴TransactionScope的方式,定义了一个DependentContext(对应着DependentTransaction)。在异步操作开始执行之前,我们根据当前ExecutionContext创建一个DependentContext,此时当前ExecutionContext相应数据项会拷贝到DependentContext中。在异步操作代码中,我们根据DependentContext创建ExecutionContextScope ,那么通过Current属性返回的实际上就是这么一个DependentContext。由于DependentContext和当前ExecutionContext各自具有自己的数据容器,针对它们的操作互不影响。如下所示的相应的编程方式:

using (ExecutionContextScope contextScope1 = new ExecutionContextScope())
{
    ExecutionContext.Current.SetValue(name, value1);
    
    ExecutionContext.Current.SetValue(name, value2);

     Task.Factory.StartNew(() =>
        {
            using (ExecutionContextScope contextScope2 = new ExecutionContextScope())
            {
                 string value1 = ExecutionContext.Current.GetValue<string>(name);
            }
        });
}

相应的编程方式,已经异步线程和当前线程上下文的独立性也可以通过如下所示的Unit Test代码来体现。

[TestMethod]
public void SetAndGetContexts2()
{
    string name = Guid.NewGuid().ToString();
    string value1 = Guid.NewGuid().ToString();
    string value2 = Guid.NewGuid().ToString();

    //1. Change current ApplicationContext will never affect the DependentContext.
    using (ExecutionContextScope contextScope1 = new ExecutionContextScope())
    {
        ExecutionContext.Current.SetValue(name, value1);
        DependentContext depedencyContext = ExecutionContext.Current.DepedentClone();
        ExecutionContext.Current.SetValue(name, value2);

        Task<string> task = Task.Factory.StartNew<string>(() =>
            {
                using (ExecutionContextScope contextScope2 = new ExecutionContextScope(depedencyContext))
                {
                    return ExecutionContext.Current.GetValue<string>(name);
                }
            });

        Assert.AreEqual<string>(value1, task.Result);
        Assert.AreEqual<string>(value2, ExecutionContext.Current.GetValue<string>(name));
    }

    //2. Change DependentContext will never affect the current ApplicationContext.
    using (ExecutionContextScope contextScope1 = new ExecutionContextScope())
    {
        ExecutionContext.Current.SetValue(name, value1);
        DependentContext depedencyContext = ExecutionContext.Current.DepedentClone();
        Task<string> task = Task.Factory.StartNew<string>(() =>
        {
            using (ExecutionContextScope contextScope2 = new ExecutionContextScope(depedencyContext))
            {
                ExecutionContext.Current.SetValue(name, value2);
                return ExecutionContext.Current.GetValue<string>(name);
            }
        });

        Assert.AreEqual<string>(value2, task.Result);
        Assert.AreEqual<string>(value1, ExecutionContext.Current.GetValue<string>(name));
    }
}

三、ExecutionContext

现在我们来讨论具体的设计和实现,先来看看表示当前执行上下文的ExecutionContext的定义。如下面的代码片段所示,ExecutionContext实际上是利用了通过Items属性表示的字典对象作为保存数据的容器,GetValue和SetValue实际上就是针对该字典的操作。表示当前ExecutionContext的静态属性Current实际上是返回一个应用了ThreadStaticAttribute特性的静态字段current,意味着ExecutionContext是基于某个线程的,每个线程的当前ExecutionContext是不同的。方法DepedentClone用于创建DependentContext 以实现当前上下文数据向异步线程的传递。

[Serializable]
public class ExecutionContext
{
    
    private static ExecutionContext current;
    public IDictionary<string, object> Items { get; internal set; }
    internal ExecutionContext()
    {
        this.Items = new Dictionary<string, object>();
    }

    public T GetValue<T>(string name, T defaultValue = default(T))
    {
        object value;
        if (this.Items.TryGetValue(name, out value))
        {
            return (T)value;
        }
        return defaultValue;
    }
    public void SetValue(string name, object value)
    {
        this.Items[name] = value;
    }

    public static ExecutionContext Current
    {
        get { return current; }
        internal set { current = value; }
    }
    public DependentContext DepedentClone()
    {
        return new DependentContext(this);
    }
}

四、DependentExecutionContext

如下所示的DependentContext的定义,它是ExecutionContext的子类。我们我们根据指定的ExecutionContext 对象创建一个DependentContext对象的时候,它的上下文数据项会自动拷贝到创建的DependentContext之中。

[Serializable]
public class DependentContext: ExecutionContext
{
    public Thread OriginalThread { get; private set; }
    public DependentContext(ExecutionContext context)
    {
       this.OriginalThread = Thread.CurrentThread;
this.Items = new Dictionary<string, object>(context.Items); } }

五、ExecutionContextScope

如下所示的是ExecutionContextScope的定义,它实现了IDisposable接口。在ExecutionContextScope被创建之前,当前ExecutionContext 被保存下来。第一个构造函数根据指定的ExecutionContextOption来对当前ExecutionContext 进行相应的设置;第二个构造函数则直接将指定的DependentContext 作为当前的ExecutionContext 。

public class ExecutionContextScope:IDisposable
{
    private ExecutionContext originalContext = ExecutionContext.Current;
    public ExecutionContextScope(ExecutionContextOption contextOption = ExecutionContextOption.Required)
    {
        switch (contextOption)
        {
            case ExecutionContextOption.RequiresNew:
                {
                    ExecutionContext.Current = new ExecutionContext();
                    break;
                }
            case ExecutionContextOption.Required:
                {
                    ExecutionContext.Current = originalContext ?? new ExecutionContext();
                    break;
                }
            case ExecutionContextOption.Suppress:
                {
                    ExecutionContext.Current = null;
                    break;
                }
        }
    }
    public ExecutionContextScope(DependentContext dependentContext)
    {
        if (dependentContext.OriginalThread == Thread.CurrentThread)
        {
            throw new InvalidOperationException("The DependentContextScope cannot be created in the thread in which the DependentContext is created.");
        }
        ExecutionContext.Current = dependentContext;
    }
    public void Dispose()
    {
        ExecutionContext.Current = originalContext;
    }
}

作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
2月前
|
移动开发 前端开发 HTML5
Twaver-HTML5基础学习(20)数据容器(3)_数据的批量加载(节省性能方法)
本文介绍了Twaver HTML5中数据的批量加载方法,通过使用`box.startBatch()`可以在大量数据加载时提高性能。文章通过示例代码展示了如何在React组件中使用批量加载功能,以减少界面重绘次数并提升效率。
53 1
Twaver-HTML5基础学习(20)数据容器(3)_数据的批量加载(节省性能方法)
|
2月前
|
XML 存储 JSON
Twaver-HTML5基础学习(19)数据容器(2)_数据序列化_XML、Json
本文介绍了Twaver HTML5中的数据序列化,包括XML和JSON格式的序列化与反序列化方法。文章通过示例代码展示了如何将DataBox中的数据序列化为XML和JSON字符串,以及如何从这些字符串中反序列化数据,重建DataBox中的对象。此外,还提到了用户自定义属性的序列化注册方法。
42 1
|
3月前
|
存储 消息中间件 容器
当一个 Pod 中包含多个容器时,容器间共享一些重要的资源和环境,这使得它们能够更有效地协同工作和交互。
当一个 Pod 中包含多个容器时,容器间共享一些重要的资源和环境,这使得它们能够更有效地协同工作和交互。
|
5月前
|
存储 索引 Python
Python基础第五篇(Python数据容器)
Python基础第五篇(Python数据容器)
|
2月前
|
XML 移动开发 JSON
Twaver-HTML5基础学习(18)数据容器(1)_增删查改、遍历数据容器、包含网元判断
本文介绍了Twaver HTML5中的数据容器(DataBox),包括如何进行增删查改操作、遍历数据容器以及判断网元是否存在于数据容器中。DataBox用于管理所有的网元对象,如ElementBox、LayerBox、AlarmBox等,并通过示例代码展示了其常用方法的使用。
46 1
Twaver-HTML5基础学习(18)数据容器(1)_增删查改、遍历数据容器、包含网元判断
|
2月前
|
存储 索引 Python
python中的数据容器
python中的数据容器
|
3月前
|
存储 Kubernetes 安全
如何与不同节点共享 Docker 容器
【8月更文挑战第27天】
56 5
|
3月前
|
安全 网络安全 数据安全/隐私保护
云原生技术探索:容器化与微服务架构的实践之路网络安全与信息安全:保护数据的关键策略
【8月更文挑战第28天】本文将深入探讨云原生技术的核心概念,包括容器化和微服务架构。我们将通过实际案例和代码示例,展示如何在云平台上实现高效的应用部署和管理。文章不仅提供理论知识,还包含实操指南,帮助开发者理解并应用这些前沿技术。 【8月更文挑战第28天】在数字化时代,网络安全和信息安全是保护个人和企业数据的前线防御。本文将探讨网络安全漏洞的成因、加密技术的应用以及提升安全意识的重要性。文章旨在通过分析网络安全的薄弱环节,介绍如何利用加密技术和提高用户警觉性来构建更为坚固的数据保护屏障。
|
3月前
|
存储 Docker 容器
在Docker中,容器退出后,通过docker ps命令查看不到,数据会丢失么?
在Docker中,容器退出后,通过docker ps命令查看不到,数据会丢失么?
|
4月前
|
Shell Linux Docker
docker常用命令大全(基础、镜像、容器、数据卷)
这些命令仅仅是 Docker 命令行工具的冰山一角,但对于日常操作来说已经非常全面。通过熟练地使用这些基础命令,用户可以有效地管理 Docker 的镜像、容器、数据卷和网络。随着用户对 Docker 的深入使用,更高级的命令和选项将会变得必需,但上面列出的命令已经为用户提供了一个坚实的起点。对于初学者来说,理解和掌握这些常用命令是深入学习 Docker 的基础。
411 5
docker常用命令大全(基础、镜像、容器、数据卷)

热门文章

最新文章