.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)上下文、最后执行线程等信息。
其包含主要信息的伪代码如下:

以下为引用: 

public sealed class ExecutionContext 
 { 
 
   internal HostExecutionContext HostExecutionContext { get; set; }
   internal IllogicalCallContext IllogicalCallContext { get; set; }
   internal LogicalCallContext LogicalCallContext { get; set; }
   internal SecurityContext SecurityContext { set; }
   internal SynchronizationContext SynchronizationContext { get; set; }
   internal Thread Thread { set; }
 }


 

其次,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 运行代码:

以下为引用: 

namespace nsfocus
 { 
 
   class ExecutionContextSample
   { 
 
     static void DemandPermission()
     { 
 
       try
       { 
 
         Console.WriteLine("In the thread executing a Demand for FileDialogPermission.";
         new FileDialogPermission(FileDialogPermissionAccess.OpenSave).Demand();
         Console.WriteLine("Successfully demanded FileDialogPermission.";
       }
       catch (Exception e)
       { 
 
         Console.WriteLine(e.Message);
       }
     }    private static void testPermission(object state)
     { 
 
       Thread t1 = new Thread(new ThreadStart(DemandPermission));
       t1.Start();
       t1.Join();
     }    static void Main()
     { 
 
       try
       { 
 
         FileDialogPermission fdp = new FileDialogPermission(FileDialogPermissionAccess.OpenSave);        testPermission(null); // test 1
        ExecutionContext ctxt = ExecutionContext.Capture();
        fdp.Deny();
        testPermission(null); // test 2
        ExecutionContext.Run(ctxt, new ContextCallback(testPermission), null); // test 3
        testPermission(null); // test 4
       }
       catch (Exception e)
       { 
 
         Console.WriteLine(e.Message);
       }
     }  
   }
 }


 

缺省情况下本机代码具有打开文件对话框的权限,因此 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() 方法来屏蔽,示例如下:

以下为引用: 

static void Main()
 { 
 
   try
   { 
 
     FileDialogPermission fdp = new FileDialogPermission(
         FileDialogPermissionAccess.OpenSave);    testPermission(null); // test 1
    ExecutionContext ctxt = ExecutionContext.Capture();
    fdp.Deny();
    testPermission(null); // test 2
    AsyncFlowControl aFC = ExecutionContext.SuppressFlow();
    testPermission(null); // test 3
    aFC.Undo();
    testPermission(null); // test 4
   }
 }


 

test 2 因为执行上下文缺省自动传播性显式失败;而调用 SuppressFlow 方法后 test 3 将成功;最终 Undo 恢复到原本的传播设置,导致 test 4 显式失败。值得注意的是调用 Undo 的线程必须跟调用 SuppressFlow 方法的线程相同,并且具有相同的执行上下文。

除了 Capture 之外,还可以直接通过 Thread.ExecutionContext 属性获取线程的执行上下文。但这种方式获取的执行上下文被关联到特定线程,不能直接在其它线程中使用,示例如下:

以下为引用: 

class ExecutionContextSample
 { 
 
   private static ExecutionContext _ctxt;  private static void testContext()
   { 
 
     try
     { 
 
       ExecutionContext.Run(_ctxt, new ContextCallback(doDemandPermission), null);
     }
     catch (Exception e)
     { 
 
       Console.WriteLine(e.Message);
     }
   }
   
 static void Main()
 { 
 
   try
   { 
 
     FileDialogPermission fdp = new FileDialogPermission(
         FileDialogPermissionAccess.OpenSave);    fdp.Deny();
    _ctxt = Thread.CurrentThread.ExecutionContext;
    Thread t1 = new Thread(new ThreadStart(testContext));
    t1.Start();
     t1.Join();
   }
   catch (Exception e)
   { 
 
     Console.WriteLine(e.Message);
   }
 } 
 }


 

这里因为直接在新线程里面,使用从主线程中通过 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...