以上下文(Context)的形式创建一个共享数据的容器
在很多情况下我们具有这样的需求:为一组相关的操作创建一个执行上下文并提供一个共享的数据容器,而不是简单地定义一个全局变量,或者将数据通过参数传来传去。这样的上下文一般具有其生命周期,它们在目标操作开始执行的时候被激活,在执行完成之后被回收。该上下文一般不能跨越多个线程,以避免多个线程操作相同的数据容器造成数据的不一致。针对这个需求,我们写了一个非常简单的例子,有兴趣的朋友可以看看。[源代码从这里下载]
目录 一、ExecutionContext的基本编程方式 二、异步调用的问题 三、ExecutionContext 四、DependentExecutionContext 五、ExecutionContextScope
一、ExecutionContext的基本编程方式
我将这个作为数据容器的上下文命名为ExecutionContext,我完全借鉴了TransactionScope的编程方式来设计这个ExecutionContext。如下的代码片段体现了ExecutionContext最基本的编程方式:我们通过ExecutionContextScope 来创建当前ExecutionContext,并且控制它的生命周期。当前ExecutionContext通过静态属性Current获取。我们分别调用GetValue和SaveValue进行上下文数据项的获取和设置。
<!-- .csharpcode {border: 1px solid gray; margin-bottom:20px;} .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } -->
using (ExecutionContextScope contextScope = new ExecutionContextScope())
{
//Set
ExecutionContext.Current.SetValue(“ActivityID”, “A001”);
//Get
string activityId = ExecutionContext.Current.GetValue<string>(“ActivityID”)
}
<!-- .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } -->
和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))
{
//...
}
}
<!-- .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } -->
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;
}
}
- MySQL无法创建表的问题分析(r12笔记第73天)
- Golang语言社区--【H5游戏开发基础知识】JavaScript 用法
- Oracle中的PGA监控报警分析二(r12笔记第87天)
- Oracle 12c PDB的数据备份恢复(r12笔记第84天)
- MySQL和Oracle中唯一性索引的差别(r12笔记第83天)
- 如何用JavaScript进行数组去重
- Oracle 12.1升级到12.2的两种方法(r12笔记第92天)
- Oracle数据库重启后密码失效的问题(r12笔记第91天)
- Oracle和MySQL竟然可以这么写这样的SQL?(r12笔记第99天)
- Golang语言社区--了解C++ 用libcurl库进行http通讯网络编程
- Golang语言-- 小技巧
- MySQL Shell的简单介绍(r12笔记第95天)
- MYSQL索引条件下推的简单测试
- 教你如何用AST语法树对代码“动手脚”
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- c语言实现TCP的socket通信
- 多文件目录Makefile的写法
- 一个有趣的例子带你入门canvas
- GitLab 12 跨版本 13 升级
- 【Rust日报】2020-07-28 SO:在命令行下浏览StackOverflow
- 【翻译】200行代码讲透RUST FUTURES (6)
- MPU6050姿态解算方式1-DMP
- 打卡群刷题总结0729——分隔链表
- 单细胞tSNE细胞降维图还可以这样做?!
- 从IIC实测波形入手,搞懂IIC通信
- FreeRTOS例程4-串口DMA收发不定长数据
- FreeRTOS例程3-串口中断接收不定长的数据与二值信号量的使用
- 前端如何将json数据导出为excel文件
- 使用Postman访问腾讯云API3.0
- C语言将float拆分为4个hex传输与重组