以上下文(Context)的形式创建一个共享数据的容器
以上下文(Context)的形式创建一个共享数据的容器
在很多情况下我们具有这样的需求:为一组相关的操作创建一个执行上下文并提供一个共享的数据容器,而不是简单地定义一个全局变量,或者将数据通过参数传来传去。这样的上下文一般具有其生命周期,它们在目标操作开始执行的时候被激活,在执行完成之后被回收。该上下文一般不能跨越多个线程,以避免多个线程操作相同的数据容器造成数据的不一致。针对这个需求,我们写了一个非常简单的例子,有兴趣的朋友可以看看。[源代码从 这里 下载]
目录
一、ExecutionContext的基本编程方式
一、ExecutionContext的基本编程方式
二、异步调用的问题
三、ExecutionContext
四、DependentExecutionContext
五、ExecutionContextScope我将这个作为数据容器的上下文命名为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;五、ExecutionContextScope
this .Items = new Dictionary< string , object >(context.Items); } }如下所示的是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/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
分类: [14] 框架设计
作者: Leo_wl
出处: http://www.cnblogs.com/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息查看更多关于以上下文(Context)的形式创建一个共享数据的容器的详细内容...
声明:本文来自网络,不代表【好得很程序员自学网】立场,转载请注明出处:http://haodehen.cn/did46167