[草稿] Managed COM Apartment - ExecutionContext [1]
Posted on 2005-12-30 01:36 Flier Lu 阅读(4569) 评论(5) 编辑 收藏 举报.NET 2.0 在底层实现上的一个非常重要的改进,是在引入了 ExecutionContext 的概念。从而引入了一个类似 COM 中套间 (Apartment) 概念的一个逻辑上的执行环境。这在很大程度上改变了 .NET 1.x 中对线程以及其执行环境的管理机制,并进而可以实现非常灵活的控制机制。因为此概念涉及到的方面非常庞杂,且现阶段可参考的资料非常少,笔者希望能通过一个系列文章,对其进行一个较为全面的分析。
首先,ExecutionContext 是一个容器,MSDN 如是说:
The ExecutionContext class provides a single container for all information relevant to a logical thread of execution. This includes security context, call context, synchronization context, localization context, and transaction context.
也就是说在 ExecutionContext 中,包括了一个逻辑线程执行代码所需要的各种上下文信息。除了文档中明确指出的安全、调用、同步、本地化和事务上下文外,实现上还可提供 CLR 宿主(Host)上下文、最后执行线程等信息。
其包含主要信息的伪代码如下:
2{
3 internal HostExecutionContext HostExecutionContext { get; set; }
4 internal IllogicalCallContext IllogicalCallContext { get; set; }
5 internal LogicalCallContext LogicalCallContext { get; set; }
6 internal SecurityContext SecurityContext { set; }
7 internal SynchronizationContext SynchronizationContext { get; set; }
8 internal Thread Thread { set; }
9}
其次,ExecutionContext 是一个快照,MSDN 如是说:
The ExecutionContext class provides the functionality for user code to capture and transfer this context across user-defined asynchronous points. The common language runtime ensures that the ExecutionContext is consistently transferred across runtime-defined asynchronous points within the managed process.
ExecutionContext 的一个典型应用场景,是捕获 (capture) 执行环境,并将其传递给一个用户自定义的异步执行点。CLR 确保在使用此 ExecutionContext 的异步执行点,能获得与捕获快照相一致的行为。
例如以下代码演示了如何使用 ExecutionContext.Capture 捕获执行环境快照,并在其环境中通过 ExecutionContext.Run 运行代码:
2{
3 class ExecutionContextSample
4 {
5 static void DemandPermission()
6 {
7 try
8 {
9 Console.WriteLine("In the thread executing a Demand for FileDialogPermission.");
10 new FileDialogPermission(FileDialogPermissionAccess.OpenSave).Demand();
11 Console.WriteLine("Successfully demanded FileDialogPermission.");
12 }
13 catch (Exception e)
14 {
15 Console.WriteLine(e.Message);
16 }
17 }
18
19 private static void testPermission(object state)
20 {
21 Thread t1 = new Thread(new ThreadStart(DemandPermission));
22 t1.Start();
23 t1.Join();
24 }
25
26 static void Main()
27 {
28 try
29 {
30 FileDialogPermission fdp = new FileDialogPermission(FileDialogPermissionAccess.OpenSave);
31
32 testPermission(null); // test 1
33
34 ExecutionContext ctxt = ExecutionContext.Capture();
35
36 fdp.Deny();
37
38 testPermission(null); // test 2
39
40 ExecutionContext.Run(ctxt, new ContextCallback(testPermission), null); // test 3
41
42 testPermission(null); // test 4
43 }
44 catch (Exception e)
45 {
46 Console.WriteLine(e.Message);
47 }
48 }
49 }
50}
缺省情况下本机代码具有打开文件对话框的权限,因此 test 1 将显式成功信息;而一旦我们显式调用了 CodeAccessPermission.Deny 方法,CLR 的安全管理器会在调用堆栈的最后一个安全帧中,设置相应标志禁用此权限。(详细的分析可以参考我 blog 上另外一篇文章《CLR 中代码访问安全检测实现原理》)
因此 test 2 和 4 的执行将显式异常信息,类似如下:
In the thread executing a Demand for FileDialogPermission.
Request for the permission of type 'System.Security.Permissions.FileDialogPermis
sion, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e08
9' failed.
test 3 虽然是在 2 和 4 之间调用,但其通过 ExecutionContext.Run 方法,以 Deny 权限之前的执行上下文执行,因此会显式成功信息。与安全权限类似,执行上下文还可以提供对同步、事务等等更多类型上下文的支持。
而通过上述例子我们也可以发现,即使是切换线程的调用,当前执行环境本身也会在 Thread.Start 方法被调用时,传播到新建的线程中。唯一的特例是调用 ThreadPool.UnsafeQueueUserWorkItem 方法新增线程池任务,因为对这种情况下无法将 CLR 的调用堆栈保留,任务将直接在完成端口 (IOCP) 的新建 CLR 上下文中执行。当然也可以调用 Capture 和 CreateCopy 方法获取目标执行上下文,并在线程池工作方法中通过 Run 来执行。
如果要禁用这一执行上下文自动传播的语义,可以通过 ExecutionContext.SuppressFlow() 方法来屏蔽,示例如下:
2{
3 try
4 {
5 FileDialogPermission fdp = new FileDialogPermission(
6 FileDialogPermissionAccess.OpenSave);
7
8 testPermission(null); // test 1
9
10 ExecutionContext ctxt = ExecutionContext.Capture();
11
12 fdp.Deny();
13
14 testPermission(null); // test 2
15
16 AsyncFlowControl aFC = ExecutionContext.SuppressFlow();
17
18 testPermission(null); // test 3
19
20 aFC.Undo();
21
22 testPermission(null); // test 4
23 }
24}
test 2 因为执行上下文缺省自动传播性显式失败;而调用 SuppressFlow 方法后 test 3 将成功;最终 Undo 恢复到原本的传播设置,导致 test 4 显式失败。值得注意的是调用 Undo 的线程必须跟调用 SuppressFlow 方法的线程相同,并且具有相同的执行上下文。
除了 Capture 之外,还可以直接通过 Thread.ExecutionContext 属性获取线程的执行上下文。但这种方式获取的执行上下文被关联到特定线程,不能直接在其它线程中使用,示例如下:
2{
3 private static ExecutionContext _ctxt;
4
5 private static void testContext()
6 {
7 try
8 {
9 ExecutionContext.Run(_ctxt, new ContextCallback(doDemandPermission), null);
10 }
11 catch (Exception e)
12 {
13 Console.WriteLine(e.Message);
14 }
15 }
16
17 static void Main()
18 {
19 try
20 {
21 FileDialogPermission fdp = new FileDialogPermission(
22 FileDialogPermissionAccess.OpenSave);
23
24 fdp.Deny();
25
26 _ctxt = Thread.CurrentThread.ExecutionContext;
27
28 Thread t1 = new Thread(new ThreadStart(testContext));
29
30 t1.Start();
31 t1.Join();
32 }
33 catch (Exception e)
34 {
35 Console.WriteLine(e.Message);
36 }
37 }
38}
这里因为直接在新线程里面,使用从主线程中通过 Thread.CurrentThread.ExecutionContext 属性获取的执行上下文,会导致一个 System.InvalidOperationException 的异常,错误信息如下
Cannot apply a context that has been marshaled across AppDomains, that was not acquired through a Capture operation or that has already been the argument to a Set call.
正确的方式是直接使用 ExecutionContext.Capture() 捕获执行上下文,或者调用 ExecutionContext.CreateCopy 方法复制线程的执行上下文。这两种方式获取的执行上下文都是与具体线程无关的。
执行上下文的另一个重要作用,是用来存储同属一个逻辑线程的调用上下文中的变量。例如我们调用一个 COM+ 组件,并在组件中向调用上下文 (CallContext) 中存储数据。如果存储的数据实现了 ILogicalThreadAffinative 接口,则 CLR 会在调用者的执行上下文中提供此数据,并可通过 CreateCopy 方式传播到其它地方。
以上我们大概从使用层面简单的介绍了执行上下文 (ExecutionContext),后续章节我们将从实现到应用进一步深入了解此概念的重要意义,尤其是在 .NET 2.0 环境下进行 COM+/Indigo 开发时,对此概念的理解能大大加深我们对技术的认识。
to be continue...