C#综合细说进程、应用程序域与上下文
引言
本文主要是介绍进程(Process)、应用程序域(AppDomain)、.NET上下文(Context)的概念与操作。
虽然在一般的开发当中这三者并不常用,但熟悉三者的关系,深入了解其作用,对提高系统的性能有莫大的帮助。
在本篇最后的一节当中将会介绍到三者与线程之间的关系,希望对多线程开发人员能提供一定的帮助。
目录
一、进程的概念与作用
进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。进程之间是相对独立的,一个进程无法访问另一个进程的 数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。
1.1 Process 的属性与方法
在 System.Diagnostics 命名空间当中存在Process类,专门用于管理进程的开始、结束,访问进程中的模块,获取进程中的线程,设定进程的优先级别等功能。表1.0 显示了Process类的常用属性:
| 属性 | 说明 |
| BasePriority | 获取关联进程的基本优先级。 |
| ExitCode | 获取关联进程终止时指定的值。 |
| ExitTime | 获取关联进程退出的时间。 |
| Handle | 返回关联进程的本机句柄。 |
| HandleCount | 获取由进程打开的句柄数。 |
| HasExited | 获取指示关联进程是否已终止的值。 |
| Id | 获取关联进程的唯一标识符。 |
| MachineName | 获取关联进程正在其上运行的计算机的名称。 |
| MainModule | 获取关联进程的主模块。 |
| Modules | 获取已由关联进程加载的模块。 |
| PriorityClass | 获取或设置关联进程的总体优先级类别。 |
| ProcessName | 获取该进程的名称。 |
| StartInfo | 获取或设置要传递给Process的Start方法的属性。 |
| StartTime | 获取关联进程启动的时间。 |
| SynchronizingObject | 获取或设置用于封送由于进程退出事件而发出的事件处理程序调用的对象。 |
| Threads | 获取在关联进程中运行的一组线程 |
表1.0
除了上述属性,Process类也定义了下列经常使用的方法:
| 方法 | 说明 |
| GetProcessById | 创建新的 Process 组件,并将其与您指定的现有进程资源关联。 |
| GetProcessByName | 创建多个新的 Process 组件,并将其与您指定的现有进程资源关联。 |
| GetCurrentProcess | 获取新的 Process 组件并将其与当前活动的进程关联。 |
| GetProcesses | 获取本地计算机上正在运行的每一个进程列表。 |
| Start | 启动一个进程。 |
| Kill | 立即停止关联的进程。 |
| Close | 释放与此组件关联的所有资源。 |
| WaitForExit | 指示 Process 组件无限期地等待关联进程退出。 |
表1.1
Process类的详细信息可以参考 http://msdn.microsoft.com/zh-cn/library/system.diagnostics.process.aspx
下面将举例详细介绍一下Process的使用方式
1.2 建立与销毁进程
利用Start()与Kill()方法可以简单建立或者销毁进程,下面例子就是利用 Start 方法启动记事本的进程,并打开File.txt文件。2秒钟以后,再使用 Kill 方法销毁进程,并关闭记事本。
1 static void Main(string[] args) |
1.3 列举计算机运行中的进程
在表1.0 中可以看到,使用 GetProcesses 方法可以获取本地计算机上正在运行的每一个进程列表。
而进程的 Id 属性是每个进程的唯一标志,通过下面的方法,可以显示当前计算机运行的所有进程信息。
因为篇幅关系,下面例子只获取前10个进程。
1 static void Main(string[] args) |
运行结果

如果已知进程的Id,就可以通过 GetProcessById 方法获取对应的进程。
1 static void Main(string[] args) |
同样地,你也可能通过GetProcessByName方法获取多个对应名称的进程。
注意:如果不能找到当前ID的进程,系统就会抛出ArgumentException异常。所以使用方法 GetProcessById 获取进程时应该包含在 try{...} catch{..} 之内。
1.4 获取进程中的多个模块
在表1.0 中包含了Modules属性,通过此属性可能获取进程中的多个模块。
这些模块可以是以 *.dll 结尾的程序集,也可以是 *.exe 结尾的可执行程序。
下面的例子就是通过 Process 的 GetCurrentProcess 方法获取当前运行的进程信息,然后显示当前进程的多个模块信息。
1 static void Main(string[] args) |
运行结果:

二、应用程序域
使用.NET建立的可执行程序 *.exe,并没有直接承载在进程当中,而是承载到应用程序域(AppDomain)当中。应用程序域是.NET引入的一个新概念,它比进程所占用的资源要少,可以被看作是一个轻量级的进程。
在一个进程中可以包含多个应用程序域,一个应用程序域可以装载一个可执行程序(*.exe)或者多个程序集(*.dll)。这样可以使应用程序域之间实现深度隔离,即使进程中的某个应用程序域出现错误,也不会影响其他应用程序域的正常运作。
当一个程序集同时被多个应用程序域调用时,会出现两种情况
第一种情况:CLR分别为不同的应用程序域加载此程序集。
第二种情况:CLR把此程序集加载到所有的应用程序域之外,并实现程序集共享,此情况比较特殊,被称作为Domain Neutral。
2.1 AppDomain的属性与方法
在System命名空间当中就存在AppDomain类,用管理应用程序域。下面是AppDomain类的常用属性:
| 属性 | 说明 |
| ActivationContext | 获取当前应用程序域的激活上下文。 |
| ApplicationIdentity | 获得应用程序域中的应用程序标识。 |
| BaseDirectory | 获取基目录。 |
| CurrentDomain | 获取当前 Thread 的当前应用程序域。 |
| Id | 获得一个整数,该整数唯一标识进程中的应用程序域。 |
| RelativeSearchPath | 获取相对于基目录的路径,在此程序集冲突解决程序应探测专用程序集。 |
| SetupInformation | 获取此实例的应用程序域配置信息。 |
表2.0
AppDomain类中有多个方法,可以用于创建一个新的应用程序域,或者执行应用程序域中的应用程序。
| 方法 | 说明 |
| CreateDomain | 创建新的应用程序域。 |
| CreateInstance | 创建在指定程序集中定义的指定类型的新实例。 |
| CreateInstanceFrom | 创建在指定程序集文件中定义的指定类型的新实例。 |
| DoCallBack | 在另一个应用程序域中执行代码,该应用程序域由指定的委托标识。 |
| ExecuteAssembly | 执行指定文件中包含的程序集。 |
| ExecuteAssemblyByName | 执行程序集。 |
| GetAssemblies | 获取已加载到此应用程序域的执行上下文中的程序集。 |
| GetCurrentThreadId | 获取当前线程标识符。 |
| GetData | 为指定名称获取存储在当前应用程序域中的值。 |
| IsDefaultAppDomain | 返回一个值,指示应用程序域是否是进程的默认应用程序域。 |
| SetData | 为应用程序域属性分配值。 |
| Load | 将 Assembly 加载到此应用程序域中。 |
| Unload | 卸载指定的应用程序域。 |
表2.1
AppDomain类中有多个事件,用于管理应用程序域生命周期中的不同部分。
| 事件 | 说明 |
| AssemblyLoad | 在加载程序集时发生。 |
| AssemblyResolve | 在对程序集的解析失败时发生。 |
| DomainUnload | 在即将卸载 AppDomain 时发生。 |
| ProcessExit | 当默认应用程序域的父进程存在时发生。 |
| ReflectionOnlyAssemblyResolve | 当程序集的解析在只反射上下文中失败时发生。 |
| ResourceResolve | 当资源解析因资源不是程序集中的有效链接资源或嵌入资源而失败时发生。 |
| TypeResolve | 在对类型的解析失败时发生。 |
| UnhandledException | 当某个异常未被捕获时出现。 |
表2.2
下面将举例详细介绍一下AppDomain的使用方式
2.2 在AppDomain中加载程序集
由表2.1中可以看到,通过CreateDomain方法可以建立一个新的应用程序域。
下面的例子将使用CreateDomain建立一个应用程序域,并使用Load方法加载程序集Model.dll。最后使用GetAssemblies方法,列举此应用程序域中的所有程序集。
1 static void Main(string[] args) |
运行结果

注意:当加载程序集后,就无法把它从AppDomain中卸载,只能把整个AppDomain卸载。
当需要在AppDomain加载可执行程序时,可以使用ExecuteAssembly方法。
AppDomain.ExecuteAssembly("Example.exe");
2.3 卸载AppDomain
通过Unload可以卸载AppDomain,在AppDomain卸载时将会触发DomainUnload事件。
下面的例子中,将会使用CreateDomain建立一个名为NewAppDomain的应用程序域。然后建立AssemblyLoad的事件处理方法,在程序集加载时显示程序集的信息。最后建立DomainUnload事件处理方法,在AppDomain卸载时显示卸载信息。
1 static void Main(string[] args) |
运行结果

2.4 在AppDomain中建立指定程序集中指定类的对象
使用CreateInstance方法,能建立程序集中指定类的对像。但使用此方法将返回一个ObjectHandle对象,若要将此值转化为原类型,可调用Unwrap方法。
下面例子会建立Model.dll程序集中的Model.Person对象。
1 namespace Test |
三、深入了解.NET上下文
3.1 .NET上下文的概念
应用程序域是进程中承载程序集的逻辑分区,在应用程序域当中,存在更细粒度的用于承载.NET对象的实体,那就.NET上下文Context。
所有的.NET对象都存在于上下文当中,每个AppDomain当中至少在于一个默认上下文(context 0)。
一般不需要指定特定上下文的对象被称为上下文灵活对象(context-agile),建立此对象不需要特定的操作,只需要由CLR自行管理,一般这些对象都会被建立在默认上下文当中。

图3.0
3.2 透明代理
在上下文的接口当中存在着一个消息接收器负责检测拦截和处理信息,当对象是MarshalByRefObject的子类的时候,CLR将会建立透明代理,实现对象与消息之间的转换
应用程序域是CLR中资源的边界,一般情况下,应用程序域中的对象不能被外界的对象所访问。而MarshalByRefObject 的功能就是允许在支持远程处理的应用程序中跨应用程序域边界访问对象,在使用.NET Remoting远程对象开发时经常使用到的一个父类。
但此文章针对的是进程与应用程序域的作用,关于.NET Remoting远程对象开发可参考:“回顾.NET Remoting分布式开发”
3.3 上下文绑定
当系统需要对象都使用消息接收器机制的时候,即可使用ContextBoundObject类。ContextBoundObject继承了MarshalByRefObject类,保证了它的子类都会通过透明代理被访问。
在第一节介绍过:一般类所建立的对象为上下文灵活对象(context-agile),它们都由CLR自动管理,可存在于任意的上下文当中。而 ContextBoundObject 的子类所建立的对象只能在建立它的对应上下文中正常运行,此状态被称为上下文绑定。其他对象想要访问ContextBoundObject 的子类对象时,都只能通过代透明理来操作。
下面的例子,是上下文绑定对象与上下文灵活对象的一个对比。Example 是一个普通类,它的对象会运行在默认上下文当中。而ContextBound类继承了ContextBoundObject,它的对象是一个上下文绑定对象。ContextBound还有一个Synchronization特性,此特性会保证ContextBound对象被加载到一个线程安全的上下文当中运行。另外,Context类存在ContextProperties属性,通过此属性可以获取该上下文的已有信息。
1 class Program |
运行结果

由运行结果可以发现,example对象一般只会工作于默认上下文context 0 当中,而contextBound则会工作于线程安全的上下文 context 1当中。当example需要调用contextBound对象时,就会通过透明代理把消息直接传递到context 1中。
四、进程、应用程序域、线程的相互关系
4.1 跨AppDomain运行代码
在应用程序域之间的数据是相对独立的,当需要在其他AppDomain当中执行当前AppDomain中的程序集代码时,可以使用CrossAppDomainDelegate委托。把CrossAppDomainDelegate委托绑定方法以后,通过AppDomain的DoCallBack方法调用执行委托。
1 static void Main(string[] args) |
运行结果

4.2 跨AppDomain的线程
线程存在于进程当中,它在不同的时刻可以运行于多个不同的AppDomain当中。它是进程中的基本执行单元,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时 系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。
关于线程的介绍,可参考 “C#综合揭秘——细说多线程(上)”、“C#综合揭秘——细说多线程(下)”
下面的例子将介绍一下如何跨AppDomain使用线程,首先建立一个ConsoleApplication项目,在执行时输入当前线程及应用程序域的信息,最后生成Example.exe的可执行程序。
1 static void Main(string[] args) |
然后再新建一个ConsoleApplication项目,在此项目中新一个AppDomain对象,在新的AppDomain中通过ExecuteAssembly方法执行Example.exe程序。
1 static void Main(string[] args) |
运行结果

可见,ID等于9的线程在不同时间内分别运行于AppDomain 1与AppDomain 2当中。
4.3 跨上下文的线程
线程既然能够跨越AppDomain的边界,当然也能跨越不同的上下文。
下面这个例子中,线程将同时运行在默认上下文与提供安全线程的上下文中。
1 class Program |
运行结果

本篇总结
进程(Process)、线程(Thread)、应用程序域(AppDomain)、上下文(Context)的关系如图5.0,一个进程内可以包括多个应用程序域,也有包括多个线程,线程也可以穿梭于多个应用程序域当中。但在同一个时刻,线程只会处于一个应用程序域内。线程也能穿梭于多个上下文当中,进行对象的调用。
虽然进程、应用程序域与上下文在平常的开发中并非经常用到,但深入地了解三者的关系,熟悉其操作方式对合理利用系统的资源,提高系统的效率是非常有意义的。
尤其是三者与线程之间的关系尤为重要,特别是在一个多线程系统中,如果不能理清其关系而盲目使用多线程,容易造成资源抢占与死锁之类的错误。

图5.0
希望本篇文章对相关的开发人员有所帮助。
http://www.uml.org.cn/net/201210264.asp
C#综合细说进程、应用程序域与上下文的更多相关文章
- [转]C#综合揭秘——细说进程、应用程序域与上下文之间的关系
引言 本文主要是介绍进程(Process).应用程序域(AppDomain)..NET上下文(Context)的概念与操作.虽然在一般的开发当中这三者并不常用,但熟悉三者的关系,深入了解其作用,对提高 ...
- C#综合揭秘——细说进程、应用程序域与上下文之间的关系
引言 本文主要是介绍进程(Process).应用程序域(AppDomain)..NET上下文(Context)的概念与操作.虽然在一般的开发当中这三者并不常用,但熟悉三者的关系,深入了解其作用,对提高 ...
- 转:C#综合揭秘——细说进程、应用程序域与上下文之间的关系
引言 本文主要是介绍进程(Process).应用程序域(AppDomain)..NET上下文(Context)的概念与操作.虽然在一般的开发当中这三者并不常用,但熟悉三者的关系,深入了解其作用,对提高 ...
- [C#参考]细说进程、应用程序域与上下文之间的关系
原文转载链接:http://www.cnblogs.com/leslies2/archive/2012/03/06/2379235.html Written by:风尘浪子 引言 本文主要是介绍进程( ...
- C# Process类_进程_应用程序域与上下文之间的关系
进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源.进程之间是相对独立的,一个进程无法直接访问另一个进程的数据(除非分布式),一个进程运行的失败也不会影响其他 ...
- Linux进程空间分布 & 上下文
Linux使用两级保护机制:0级供内核使用,3级供用户程序使用.从图中可以看出,每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的.最高的1GB字节虚拟内核空间则为所有进程 ...
- 细说进程五种状态的生老病死——双胞胎兄弟Java线程
java线程的五种状态其实要真正高清,只需要明白计算机操作系统中进程的知识,原理都是相同的. 系统根据PCB结构中的状态值控制进程. 单CPU系统中,任一时刻处于执行状态的进程只有一个. 进程的五种状 ...
- C#综合揭秘——细说多线程(上)
引言 本文主要从线程的基础用法,CLR线程池当中工作者线程与I/O线程的开发,并行操作PLINQ等多个方面介绍多线程的开发. 其中委托的BeginInvoke方法以及回调函数最为常用. 而 I/O线程 ...
- C#综合揭秘——细说多线程
一.线程的定义 1. 1 进程.应用程序域与线程的关系 进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源.进程之间是相对独立的,一个进程无法访问另一个进程 ...
随机推荐
- vivo x9i ADB 模拟点击
手机连接电脑无反应,安装360驱动大师 更多设置--关于---多次点击软件版本号--开启开发者选项 USB调试--USB模拟点击(需要密码开启)
- dataX windows10安装
按照视频课程,从Github上下载文件:https://github.com/alibaba/DataX 然后将下载的压缩包解压即可,不过需要的前提Python环境是要求python2,于是在pyth ...
- 【LeetCode】523. 连续的子数组和
523. 连续的子数组和 知识点:数组:前缀和: 题目描述 给你一个整数数组 nums 和一个整数 k ,编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组: 子数组大小 至少为 2 ,且 ...
- PAT乙级:1069 微博转发抽奖 (20分)
PAT乙级:1069 微博转发抽奖 (20分) 题干 小明 PAT 考了满分,高兴之余决定发起微博转发抽奖活动,从转发的网友中按顺序每隔 N 个人就发出一个红包.请你编写程序帮助他确定中奖名单. 输入 ...
- yaml 文件解析
前言 yaml文件其实也是一种配置文件类型,相比较ini,conf配置文件来说,更加的简洁,操作也更加简单,同时可以存放不同类型的数据,不会改变原有数据类型,所有的数据类型在读取时都会原样输出,yam ...
- 七个对我最重要的职业建议)--转载来自ruanyifeng博客
原文:http://www.ruanyifeng.com/blog/2015/09/career-advice.html 一.不要别人点什么,就做什么 我的第一份工作,只干了8个月,那家公司就倒闭了. ...
- Codeforces Round #735 (Div. 2) 题解
比赛地址:https://codeforces.com/contest/1554. 只有 ABCD 的题解,E 不会. A 答案是 \(\max_i\{a_ia_{i+1}\}\).证明:(反证)如果 ...
- Python实用案例,Python脚本,Python实现批量加水印
往期回顾 Python实现自动监测Github项目并打开网页 Python实现文件自动归类 Python实现帮你选择双色球号码 Python实现每日更换"必应图片"为"桌 ...
- 手撸一个SpringBoot-Starter
1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...
- OpenGL学习笔记(五)变换
目录 变换 向量 向量的运算 向量与标量运算 向量取反 向量加减 求向量长度 向量的单位化 向量相乘 点乘(Dot Product) 叉乘 矩阵 矩阵的加减 矩阵的数乘 矩阵相乘 矩阵与向量相乘 与单 ...