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


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

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

    目录 
    一、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.Required))
        {
            //...
        }
        using (ExecutionContextScope contextScope2 = new ExecutionContextScope(ExecutionContextOption.RequiresNew))
        {
            //...
        }
        using (ExecutionContextScope contextScope2 = new ExecutionContextScope(ExecutionContextOption.Suppress))
        {
            //...
        }
    }

    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);
        DependentContext depedencyContext = ExecutionContext.Current.DepedentClone();
        ExecutionContext.Current.SetValue(name, value2);
    
         Task.Factory.StartNew(() =>
            {
                using (ExecutionContextScope contextScope2 = new ExecutionContextScope(depedencyContext))
                {
                     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
    {
        [ThreadStatic]
        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;
        }
    }
    作者:Artech
    出处:http://artech.cnblogs.com/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
     
  • 相关阅读:
    AG-Admin微服务框架入门
    使用node-webkit包装浏览器
    转 使用Docker部署 spring-boot maven应用
    转 docker的下载与安装
    Maven基本理解
    物联网垂直态势分析
    纯技术商业价值
    OpenLayers 3 入门教程
    转 从红帽、GitHub和Docker看开源商业模式的进阶
    关于 Total Commander 的标签(Tab)功能【转】
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3021179.html
Copyright © 2020-2023  润新知