以上下文(Context)的形式创建一个共享数据的容器
2013-04-14 22:43
357 查看
以上下文(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; } }
相关文章推荐
- 以上下文(Context)的形式创建一个共享数据的容器
- angularjs2版本创建一个数组,将数组里面的内容通过数据绑定的形式循环到table表单中
- 无废话Android之listview入门,自定义的数据适配器、采用layoutInflater打气筒创建一个view对象、常用数据适配器ArrayAdapter、SimpleAdapter、使用ContentProvider(内容提供者)共享数据、短信的备份、插入一条记录到系统短信应用(3)
- C#打开或者创建一个文件,然后向其末尾写入数据的方法
- 在一个程序中需要用到全局变量(在多个class之间共享数据),请问如何定义具有这种功能的变量?或者是否有其他的方法解决多个class之间的数据共享(尽量简单实现)。 首先应该明确 Java中没有全局变
- 需求:在硬盘上,创建一个文件并写入一些文字数据。
- Android中创建一个使用ListView以及用BaseAdapter进行数据适配的程序
- 开发一个好项目:六、创建数据源,创建数据仓库
- 解决”不允许一个用户使用一个以上用户名与一个服务器或共享资源的多重连接“问题
- 输出一个整型数据的二进制形式
- 怎么用jquery创建一个斑马线形式的表格
- [转]在ASP.NET 2.0中操作数据::创建一个数据访问层
- HDFS的API调用,创建Maven工程,创建一个非Maven工程,HDFS客户端操作数据代码示例,文件方式操作和流式操作
- Android学习中关于SQLite的一个小Demo(数据库的创建、数据的增删查改)
- OpenJDK源码研究笔记(十三):Javac编译过程中的上下文容器(Context)、单例(Singleton)和延迟创建(LazyCreation)3种模式
- 创建一个Date.text文件,获取当前的日期,将日期格式为“2013/02/14 05:20:00”的形式。然后一秒钟记录一次,将新的时间存入到文件中。
- 获得一个数据在内存中存储的二进制形式
- 解决不允许一个用户使用一个以上用户名与一个服务器或共享资源的多重连接
- 一个使用c++在lua中创建自定义数据类型的简易方法
- 1005实现一个线程从共享的缓冲区中读数据,另一个线程向共享的缓冲区中写数据