热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[C#参考]细说进程、应用程序域与上下文之间的关系

原文转载链接:http:www.cnblogs.comleslies2archive201203062379235.htmlWrittenby:风尘浪子引言本文主要是介绍进程(Pr

原文转载链接:http://www.cnblogs.com/leslies2/archive/2012/03/06/2379235.html

Written by:风尘浪子


引言

本文主要是介绍进程(Process)、应用程序域(AppDomain)、.NET上下文(Context)的概念与操作。
虽然在一般的开发当中这三者并不常用,但熟悉三者的关系,深入了解其作用,对提高系统的性能有莫大的帮助。
在本篇最后的一节当中将会介绍到三者与线程之间的关系,希望对多线程开发人员能提供一定的帮助。
因为时间仓促,文中有错误的地方敬请点评。


目录

一、进程的概念与作用

二、应用程序域

三、深入了解.NET上下文

四、进程应用程序域与线程的关系


一、进程的概念与作用

进程(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             获取在关联进程中运行的一组线程

除了上述属性,Process类也定义了下列经常使用的方法:

方法                说明
GetProcessById      创建新的 Process 组件,并将其与您指定的现有进程资源关联。
GetProcessByName    创建多个新的 Process 组件,并将其与您指定的现有进程资源关联。
GetCurrentProcess   获取新的 Process 组件并将其与当前活动的进程关联。
GetProcesses        获取本地计算机上正在运行的每一个进程列表。
Start               启动一个进程。
Kill                立即停止关联的进程。
Close               释放与此组件关联的所有资源。
WaitForExit         指示 Process 组件无限期地等待关联进程退出。

1.2 建立与销毁进程

利用 Start 与Kill 方法可以简单建立或者销毁进程,下面例子就是利用 Start 方法启动记事本的进程,并打开File.txt文件。2秒钟以后,再使用 Kill 方法销毁进程,并关闭记事本。

static void Main(string[] args)
{
    Process process = Process.Start("notepad.exe","File.txt");
    Thread.Sleep(2000);
    process.Kill();
}

1.3 列举计算机运行中的进程

在表1.0 中可以看到,使用 GetProcesses 方法可以获取本地计算机上正在运行的每一个进程列表。
而进程的 Id 属性是每个进程的唯一标志,通过下面的方法,可以显示当前计算机运行的所有进程信息。
因为篇幅关系,下面例子只获取前10个进程。

static void Main(string[] args)
{
    var processList = Process.GetProcesses().OrderBy(x=>x.Id).Take(10);
    foreach (var process in processList)
        Console.WriteLine(string.Format("ProcessId is:{0} \t ProcessName is:{1}", process.Id, process.ProcessName));
    Console.ReadKey();
}

运行结果:

技术分享

如果已知进程的Id,就可以通过 GetProcessById 方法获取对应的进程。

static void Main(string[] args)
{
    try
    {
        var process = Process.GetProcessById(1772);
        Console.WriteLine("Process name is:" + process.ProcessName);
    }
    catch (ArgumentException ex)
    {
        Console.WriteLine("Process is nothing!");
    }
    Console.ReadKey();
}

同样地,你也可能通过GetProcessByName方法获取多个对应名称的进程。

注意:如果不能找到当前ID的进程,系统就会抛出ArgumentException异常。所以使用方法 GetProcessById 获取进程时应该包含在 try{...} catch{..} 之内。

1.4 获取进程中的多个模块

在表1.0 中包含了Process类的Modules属性,通过此属性可能获取进程中的多个模块。
这些模块可以是以 *.dll 结尾的程序集,也可以是 *.exe 结尾的可执行程序。
下面的例子就是通过 Process 的 GetCurrentProcess 方法获取当前运行的进程信息,然后显示当前进程的多个模块信息。

static void Main(string[] args)
{
    var moduleList = Process.GetCurrentProcess().Modules;
    foreach (System.Diagnostics.ProcessModule module in moduleList)
        Console.WriteLine(string.Format("{0}\n  URL:{1}\n  Version:{2}", module.ModuleName,module.FileName,module.FileVersionInfo.FileVersion));
    Console.ReadKey();
}

运行结果:

技术分享


二、应用程序域

使用.NET建立的可执行程序 *.exe,并没有直接装载到进程当中,而是装载到应用程序域(AppDomain)当中。应用程序域是.NET引入的一个新概念,它比进程所占用的资源要少,可以被看作是一个轻量级的进程。
在一个进程中可以包含多个应用程序域,一个应用程序域可以装载一个可执行程序(*.exe)或者多个程序集(*.dll)。这样可以使应用程序域之间实现深度隔离,即使进程中的某个应用程序域出现错误,也不会影响其他应用程序域的正常运作。

当一个程序集同时被多个应用程序域调用时,会出现两种情况:
第一种情况:CLR分别为不同的应用程序域加载此程序集。
第二种情况:CLR把此程序集加载到所有的应用程序域之外,并实现程序集共享,此情况比较特殊,被称作为Domain Neutral。

2.1 AppDomain的属性与方法

在System命名空间当中就存在AppDomain类,用管理应用程序域。下面是AppDomain类的常用属性:

属性                    说明
ActivationContext       获取当前应用程序域的激活上下文。
ApplicationIdentity     获得应用程序域中的应用程序标识。
BaseDirectory           获取基目录。
CurrentDomain           获取当前 Thread 的当前应用程序域。
Id                      获得一个整数,该整数唯一标识进程中的应用程序域。
RelativeSearchPath      获取相对于基目录的路径,在此程序集冲突解决程序应探测专用程序集。
SetupInformation        获取此实例的应用程序域配置信息。

AppDomain类中有多个方法,可以用于创建一个新的应用程序域,或者执行应用程序域中的应用程序。

方法                     说明
CreateDomain             创建新的应用程序域。
CreateInstance           创建在指定程序集中定义的指定类型的新实例。
CreateInstanceFrom       创建在指定程序集文件中定义的指定类型的新实例。
DoCallBack               在另一个应用程序域中执行代码,该应用程序域由指定的委托标识。
ExecuteAssembly          执行指定文件中包含的程序集。
ExecuteAssemblyByName    执行程序集。
GetAssemblies            获取已加载到此应用程序域的执行上下文中的程序集。
GetCurrentThreadId       获取当前线程标识符。
GetData                  为指定名称获取存储在当前应用程序域中的值。
IsDefaultAppDomain       返回一个值,指示应用程序域是否是进程的默认应用程序域。
SetData                  为应用程序域属性分配值。
Load                     将Assembly加载到此应用程序域中。
Unload                   卸载指定的应用程序域。

AppDomain类中有多个事件,用于管理应用程序域生命周期中的不同部分。

事件                               说明
AssemblyLoad                       在加载程序集时发生。
AssemblyResolve                    在对程序集的解析失败时发生。
DomainUnload                       在即将卸载 AppDomain 时发生。
ProcessExit                        当默认应用程序域的父进程存在时发生。
ReflectionOnlyAssemblyResolve      当程序集的解析在只反射上下文中失败时发生。
ResourceResolve                    当资源解析因资源不是程序集中的有效链接资源或嵌入资源而失败时发生。
TypeResolve                        在对类型的解析失败时发生。
UnhandledException                 当某个异常未被捕获时出现。

下面将举例详细介绍一下AppDomain的使用方式:

2.2 在AppDomain中加载程序集

由表2.1中可以看到,通过CreateDomain方法可以建立一个新的应用程序域。
下面的例子将使用CreateDomain建立一个应用程序域,并使用Load方法加载程序集Model.dll。最后使用GetAssemblies方法,列举此应用程序域中的所有程序集。

static void Main(string[] args)
        {
            var appDomain = AppDomain.CreateDomain("NewAppDomain");
            appDomain.Load("Model");
            foreach (var assembly in appDomain.GetAssemblies())
                Console.WriteLine(string.Format("{0}\n----------------------------", assembly.FullName));
            Console.ReadKey();
        }

执行结果:

技术分享

注意:当加载程序集后,就无法把它从AppDomain中卸载,只能把整个AppDomain卸载。

当需要在AppDomain加载可执行程序时,可以使用ExecuteAssembly方法。

AppDomain.ExecuteAssembly("Example.exe");

2.3 卸载AppDomain

通过Unload可以卸载AppDomain,在AppDomain卸载时将会触发DomainUnload事件。
下面的例子中,将会使用CreateDomain建立一个名为NewAppDomain的应用程序域。然后建立AssemblyLoad的事件处理方法,在程序集加载时显示程序集的信息。最后建立DomainUnload事件处理方法,在AppDomain卸载时显示卸载信息。

static void Main(string[] args)
{
    //新建名为NewAppDomain的应用程序域
    AppDomain newAppDomain = AppDomain.CreateDomain("NewAppDomain");
    //建立AssemblyLoad事件处理方法
    newAppDomain.AssemblyLoad +=
        (obj, e) =>
        {
            Console.WriteLine(string.Format("{0} is loading!", e.LoadedAssembly.GetName()));
        };
    //建立DomainUnload事件处理方法
    newAppDomain.DomainUnload +=
        (obj, e) =>
        {
            Console.WriteLine("NewAppDomain Unload!");
        };
    //加载程序集
    newAppDomain.Load("Model");
    //模拟操作
    for (int n = 0; n <5; n++)
        Console.WriteLine("  Do Work.......!");
     //卸载AppDomain
    AppDomain.Unload(newAppDomain);
    Console.ReadKey();
}

执行结果:

技术分享

2.4 在AppDomain中建立程序集中指定类的对象

使用CreateInstance方法,能建立程序集中指定类的对像。但使用此方法将返回一个ObjectHandle对象,若要将此值转化为原类型,可调用Unwrap方法。
下面例子会建立Model.dll程序集中的Model.Person对象。

namespace Test
{
     public class Program
    {
         static void Main(string[] args)
         {
             var person=(Person)AppDomain.CurrentDomain
                          .CreateInstance("Model","Model.Person").Unwrap();
             person.ID = 1;
             person.Name = "Leslie";
             person.Age = 29;
             Console.WriteLine(string.Format("{0}‘s age is {1}!",person.Name,person.Age));
             Console.ReadKey();
         }
    }
}
 
namespace Model
{
    public class Person
    {
          public int ID
          {
              get;
              set;
          }
          public string Name
          {
               get;
               set;
          }
          public int Age
          {
               get;
               set;
          }
     }
}

三、深入了解.NET上下文

3.1 .NET上下文的概念

应用程序域是进程中承载程序集的逻辑分区,在应用程序域当中,存在更细粒度的用于承载.NET对象的实体,那就.NET上下文Context。
所有的.NET对象都存在于上下文当中,每个AppDomain当中至少存在一个默认上下文(context 0)。
一般不需要指定特定上下文的对象被称为上下文灵活对象(context-agile),建立此对象不需要特定的操作,只需要由CLR自行管理,一般这些对象都会被建立在默认上下文当中。

技术分享

图3.0

3.2 透明代理

在上下文的接口当中存在着一个消息接收器负责检测拦截和处理信息,当对象是MarshalByRefObject的子类的时候,CLR将会建立透明代理,实现对象与消息之间的转换。
应用程序域是CLR中资源的边界,一般情况下,应用程序域中的对象不能被外界的对象所访问。而MarshalByRefObject 的功能就是允许在支持远程处理的应用程序中跨应用程序域边界访问对象,在使用.NET Remoting远程对象开发时经常使用到的一个父类。
此文章针对的是进程与应用程序域的作用,关于MarshalByRefObject的使用已经超越了本文的范围,关于.NET Remoting 远程对象开发可参考:“回顾.NET Remoting分布式开发”。

3.3 上下文绑定

当系统需要对象使用消息接收器机制的时候,即可使用ContextBoundObject类。ContextBoundObject继承了MarshalByRefObject类,保证了它的子类都会通过透明代理被访问。
在第一节介绍过:一般类所建立的对象为上下文灵活对象(context-agile),它们都由CLR自动管理,可存在于任意的上下文当中。而 ContextBoundObject 的子类所建立的对象只能在建立它的对应上下文中正常运行,此状态被称为上下文绑定。其他对象想要访问ContextBoundObject 的子类对象时,都只能通过代透明理来操作。

下面的例子,是上下文绑定对象与上下文灵活对象的一个对比。Example 是一个普通类,它的对象会运行在默认上下文当中。而ContextBound类继承了ContextBoundObject,它的对象是一个上下文绑定对象。ContextBound还有一个Synchronization特性,此特性会保证ContextBound对象被加载到一个线程安全的上下文当中运行。另外,Context类存在ContextProperties属性,通过此属性可以获取该上下文的已有信息。

class Program
{
    public class Example
    {
        public void Test()
        {
            ContextMessage("Example Test\n");
        }
        //访问上下文绑定对象测试
        public void Sync(ContextBound contextBound)
        {
            contextBound.Test("Example call on contextBound\n");
        }
    }
 
    [Synchronization]
    public class ContextBound:ContextBoundObject
    {
        public void Test(string message)
        {
            ContextMessage(message);
        }
    }
 
    static void Main(string[] args)
    {
        Example example = new Example();
        example.Test();
        ContextBound cOntextBound= new ContextBound();
        contextBound.Test("ContentBound Test\n");
        example.Sync(contextBound);
        Console.ReadKey();
    }
 
    //显示上下文信息
    public static void ContextMessage(string data)
    {
        Context cOntext= Thread.CurrentContext;
        Console.WriteLine(string.Format("{0}ContextId is {1}", data, context.ContextID));
        foreach (var prop in context.ContextProperties)
            Console.WriteLine(prop.Name);
        Console.WriteLine();
    }
}

执行结果:

技术分享

由运行结果可以发现,example对象一般只会工作于默认上下文context 0 当中,而contextBound则会工作于线程安全的上下文 context 1当中。当example需要调用contextBound对象时,就会通过透明代理把消息直接传递到context 1中。


四、进程、应用程序域、线程的相互关系

4.1 跨AppDomain运行代码

在应用程序域之间的数据是相对独立的,当需要在其他AppDomain当中执行当前AppDomain中的程序集代码时,可以使用CrossAppDomainDelegate委托。把CrossAppDomainDelegate委托绑定方法以后,通过AppDomain的DoCallBack方法即可执行委托。

static void Main(string[] args)
{
    Console.WriteLine("CurrentAppDomain start!");
    //建立新的应用程序域对象
    AppDomain newAppDomain = AppDomain.CreateDomain("newAppDomain");
    //绑定CrossAppDomainDelegate的委托方法
    CrossAppDomainDelegate crossAppDomainDelegate=new CrossAppDomainDelegate(MyCallBack);
    //绑定DomainUnload的事件处理方法
    newAppDomain.DomainUnload += (obj, e) =>
    {
        Console.WriteLine("NewAppDomain unload!");
    };
    //调用委托
    newAppDomain.DoCallBack(crossAppDomainDelegate);
    AppDomain.Unload(newAppDomain) ;
    Console.ReadKey();
}
 
static public void MyCallBack()
{
    string name = AppDomain.CurrentDomain.FriendlyName;
    for(int n=0;n<4;n++)
    Console.WriteLine(string.Format( "  Do work in {0}........" , name));
}

执行结果:

技术分享

4.2 跨AppDomain的线程

线程存在于进程当中,它在不同的时刻可以运行于多个不同的AppDomain当中。它是进程中的基本执行单元,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时 系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。
关于线程的介绍,可参考 “C#综合揭秘——细说多线程(上)”、“C#综合揭秘——细说多线程(下)”

下面的例子将介绍一下如何跨AppDomain使用线程,首先建立一个ConsoleApplication项目,在执行时输入当前线程及应用程序域的信息,最后生成Example.exe的可执行程序。

static void Main(string[] args)
{
    var message = string.Format("  CurrentThreadID is:{0}\tAppDomainID is:{1}",
        Thread.CurrentThread.ManagedThreadId, AppDomain.CurrentDomain.Id);
    Console.WriteLine(message);
    Console.Read();
}

然后再新建一个ConsoleApplication项目,在此项目中新一个AppDomain对象,在新的AppDomain中通过ExecuteAssembly方法执行Example.exe程序。

static void Main(string[] args)
{
    //当前应用程序域信息
    Console.WriteLine("CurrentAppDomain start!");
    ShowMessage();
    
    //建立新的应用程序域对象
    AppDomain newAppDomain = AppDomain.CreateDomain("newAppDomain");
    //在新的应用程序域中执行Example.exe
    newAppDomain.ExecuteAssembly("Example.exe");
 
    AppDomain.Unload(newAppDomain);
    Console.ReadKey();
}
 
public static void ShowMessage()
{
    var message = string.Format("  CurrentThreadID is:{0}\tAppDomainID is:{1}", Thread.CurrentThread.ManagedThreadId, AppDomain.CurrentDomain.Id);
    Console.WriteLine(message);
}

执行结果:

技术分享

可见,ID等于9的线程在不同时间内分别运行于AppDomain 1与AppDomain 2当中。

4.3 跨上下文的线程

线程既然能够跨越AppDomain的边界,当然也能跨越不同的上下文。
下面这个例子中,线程将同时运行在默认上下文与提供安全线程的上下文中。

class Program
{
    [Synchronization]
    public class ContextBound : ContextBoundObject
    {
        public void Test()
        {
            ShowMessage();
        }
    }
 
    static void Main(string[] args)
    {
        //当前应用程序域信息
        Console.WriteLine("CurrentAppDomain start!");
        ShowMessage();
 
        //在上下文绑定对象中运行线程
        ContextBound cOntextBound= new ContextBound();
        contextBound.Test();
        Console.ReadKey();
    }
 
    public static void ShowMessage()
    {
        var message = string.Format("  CurrentThreadID is:{0}\tContextID is:{1}",
             Thread.CurrentThread.ManagedThreadId, Thread.CurrentContext.ContextID);
        Console.WriteLine(message);
    }
}

执行结果:

技术分享

本篇总结

进程(Process)、线程(Thread)、应用程序域(AppDomain)、上下文(Context)的关系如图5.0,一个进程内可以包括多个应用程序域,也有包括多个线程,线程也可以穿梭于多个应用程序域当中。但在同一个时刻,线程只会处于一个应用程序域内。线程也能穿梭于多个上下文当中,进行对象的调用。

虽然进程、应用程序域与上下文在平常的开发中并非经常用到,但深入地了解三者的关系,熟悉其操作方式对合理利用系统的资源,提高系统的效率是非常有意义的。
尤其是三者与线程之间的关系尤为重要,特别是在一个多线程系统中,如果不能理清其关系而盲目使用多线程,容易造成资源抢占与死锁之类的错误。

技术分享

图5.0

希望本篇文章对相关的开发人员有所帮助。

[C#参考]细说进程、应用程序域与上下文之间的关系


推荐阅读
  • HDU1085 捕获本·拉登!
    问题描述众所周知,本·拉登是一位臭名昭著的恐怖分子,他已失踪多年。但最近有报道称,他藏匿在中国杭州!虽然他躲在杭州的一个洞穴中不敢外出,但近年来他因无聊而沉迷于数学问题,并声称如果有人能解出他的题目,他就自首。 ... [详细]
  • TunnelWarfareTimeLimit:1000MS MemoryLimit:131072KTotalSubmissions:7307 ... [详细]
  • 电子与正电子的相互作用
    本文探讨了电子与正电子之间的基本物理特性及其在现代物理学中的应用,包括它们的产生、湮灭过程以及在粒子加速器和宇宙射线中的表现。 ... [详细]
  • 这个报错出现在userDao里面,sessionfactory没有注入。解决办法:spring整合Hibernate使用test测试时要把spring.xml和spring-hib ... [详细]
  • 利用Selenium框架解决SSO单点登录接口无法返回Token的问题
    针对接口自动化测试中遇到的SSO单点登录系统不支持通过API接口返回Token的问题,本文提供了一种解决方案,即通过UI自动化工具Selenium模拟用户登录过程,从浏览器的localStorage或sessionStorage中提取Token。 ... [详细]
  • VS Code 中 .vscode 文件夹配置详解
    本文介绍了 VS Code 中 .vscode 文件夹下的配置文件及其作用,包括常用的预定义变量和三个关键配置文件:launch.json、tasks.json 和 c_cpp_properties.json。 ... [详细]
  • 深入探讨Web服务器与动态语言的交互机制:CGI、FastCGI与PHP-FPM
    本文详细解析了Web服务器(如Apache、Nginx等)与动态语言(如PHP)之间通过CGI、FastCGI及PHP-FPM进行交互的具体过程,旨在帮助开发者更好地理解这些技术背后的原理。 ... [详细]
  • 本文介绍了JSP的基本概念、常用标签及其功能,并通过示例详细说明了如何在JSP页面中使用Java代码。 ... [详细]
  • 本文介绍了多种Eclipse插件,包括XML Schema Infoset Model (XSD)、Graphical Editing Framework (GEF)、Eclipse Modeling Framework (EMF)等,涵盖了从Web开发到图形界面编辑的多个方面。 ... [详细]
  • 解决 Pytest 运行时出现 FileNotFoundError 的方法
    在使用 Pytest 进行测试时,可能会遇到 FileNotFoundError 错误,提示无法找到指定的文件或目录。本文将探讨该错误的原因及解决方案。 ... [详细]
  • Sass 是一种 CSS 的预处理器,通过使用变量、嵌套、继承等高级功能,使得 CSS 的编写更加灵活和高效。本文将介绍 Sass 的基本语法及其安装使用方法。 ... [详细]
  • 本文详细探讨Java中Scanner类的两个重要方法——nextInt()和nextDouble(),并通过实例代码演示如何使用这些方法来处理用户输入。 ... [详细]
  • J2EE平台集成了多种服务、API和协议,旨在支持基于Web的多层应用开发。本文将详细介绍J2EE平台中的13项关键技术规范,涵盖从数据库连接到事务处理等多个方面。 ... [详细]
  • 13、单向链表
    头文件:LinkList.hLinkList.cmain.cVS2 ... [详细]
  • 深入浅出:Hadoop架构详解
    Hadoop作为大数据处理的核心技术,包含了一系列组件如HDFS(分布式文件系统)、YARN(资源管理框架)和MapReduce(并行计算模型)。本文将通过实例解析Hadoop的工作原理及其优势。 ... [详细]
author-avatar
我细细
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有