C#綜合揭秘——細說進程、應用程序域與上下文之間的關系


引言

本文主要是介紹進程(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 獲取在關聯進程中運行的一組線程

 表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)
2 {
3 Process process = Process.Start("notepad.exe","File.txt");
4 Thread.Sleep(2000);
5 process.Kill();
6 }

 

1.3 列舉計算機運行中的進程

在表1.0 中可以看到,使用 GetProcesses 方法可以獲取本地計算機上正在運行的每一個進程列表。
而進程的 Id 屬性是每個進程的唯一標志,通過下面的方法,可以顯示當前計算機運行的所有進程信息。
因為篇幅關系,下面例子只獲取前10個進程。

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

運行結果

 

如果已知進程的Id,就可以通過 GetProcessById 方法獲取對應的進程。

 1         static void Main(string[] args)
2 {
3 try
4 {
5 var process = Process.GetProcessById(1772);
6 Console.WriteLine("Process name is:" + process.ProcessName);
7 }
8 catch (ArgumentException ex)
9 {
10 Console.WriteLine("Process is nothing!");
11 }
12 Console.ReadKey();
13 }


同樣地,你也可能通過GetProcessByName方法獲取多個對應名稱的進程。

注意:如果不能找到當前ID的進程,系統就會拋出ArgumentException異常。所以使用方法 GetProcessById 獲取進程時應該包含在 try{...} catch{..} 之內。

 

1.4 獲取進程中的多個模塊

在表1.0 中包含了Process類的Modules屬性,通過此屬性可能獲取進程中的多個模塊。
這些模塊可以是以 *.dll 結尾的程序集,也可以是 *.exe 結尾的可執行程序。
下面的例子就是通過 Process 的 GetCurrentProcess 方法獲取當前運行的進程信息,然后顯示當前進程的多個模塊信息。

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

運行結果:

回到目錄

 

二、應用程序域

使用.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)
2 {
3 var appDomain = AppDomain.CreateDomain("NewAppDomain");
4 appDomain.Load("Model");
5 foreach (var assembly in appDomain.GetAssemblies())
6 Console.WriteLine(string.Format("{0}\n----------------------------",
7 assembly.FullName));
8 Console.ReadKey();
9 }

運行結果

注意:當加載程序集后,就無法把它從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 {
3 //新建名為NewAppDomain的應用程序域
4 AppDomain newAppDomain = AppDomain.CreateDomain("NewAppDomain");
5 //建立AssemblyLoad事件處理方法
6 newAppDomain.AssemblyLoad +=
7 (obj, e) =>
8 {
9 Console.WriteLine(string.Format("{0} is loading!", e.LoadedAssembly.GetName()));
10 };
11 //建立DomainUnload事件處理方法
12 newAppDomain.DomainUnload +=
13 (obj, e) =>
14 {
15 Console.WriteLine("NewAppDomain Unload!");
16 };
17 //加載程序集
18 newAppDomain.Load("Model");
19 //模擬操作
20 for (int n = 0; n < 5; n++)
21 Console.WriteLine(" Do Work.......!");
22 //卸載AppDomain
23 AppDomain.Unload(newAppDomain);
24 Console.ReadKey();
25 }

運行結果

 

2.4 在AppDomain中建立程序集中指定類的對象

使用CreateInstance方法,能建立程序集中指定類的對像。但使用此方法將返回一個ObjectHandle對象,若要將此值轉化為原類型,可調用Unwrap方法。
下面例子會建立Model.dll程序集中的Model.Person對象。

 1 namespace Test
2 {
3 public class Program
4 {
5 static void Main(string[] args)
6 {
7 var person=(Person)AppDomain.CurrentDomain
8 .CreateInstance("Model","Model.Person").Unwrap();
9 person.ID = 1;
10 person.Name = "Leslie";
11 person.Age = 29;
12 Console.WriteLine(string.Format("{0}'s age is {1}!",person.Name,person.Age));
13 Console.ReadKey();
14 }
15 }
16 }
17
18 namespace Model
19 {
20 public class Person
21 {
22 public int ID
23 {
24 get;
25 set;
26 }
27 public string Name
28 {
29 get;
30 set;
31 }
32 public int Age
33 {
34 get;
35 set;
36 }
37 }
38 }


回到目錄

三、深入了解.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屬性,通過此屬性可以獲取該上下文的已有信息。

 1     class Program
2 {
3 public class Example
4 {
5 public void Test()
6 {
7 ContextMessage("Example Test\n");
8 }
9 //訪問上下文綁定對象測試
10 public void Sync(ContextBound contextBound)
11 {
12 contextBound.Test("Example call on contextBound\n");
13 }
14 }
15
16 [Synchronization]
17 public class ContextBound:ContextBoundObject
18 {
19 public void Test(string message)
20 {
21 ContextMessage(message);
22 }
23 }
24
25 static void Main(string[] args)
26 {
27 Example example = new Example();
28 example.Test();
29 ContextBound contextBound = new ContextBound();
30 contextBound.Test("ContentBound Test\n");
31 example.Sync(contextBound);
32 Console.ReadKey();
33 }
34
35 //顯示上下文信息
36 public static void ContextMessage(string data)
37 {
38 Context context = Thread.CurrentContext;
39 Console.WriteLine(string.Format("{0}ContextId is {1}", data, context.ContextID));
40 foreach (var prop in context.ContextProperties)
41 Console.WriteLine(prop.Name);
42 Console.WriteLine();
43 }
44 }

運行結果

由運行結果可以發現,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)
2 {
3 Console.WriteLine("CurrentAppDomain start!");
4 //建立新的應用程序域對象
5 AppDomain newAppDomain = AppDomain.CreateDomain("newAppDomain");
6 //綁定CrossAppDomainDelegate的委托方法
7 CrossAppDomainDelegate crossAppDomainDelegate=new CrossAppDomainDelegate(MyCallBack);
8 //綁定DomainUnload的事件處理方法
9 newAppDomain.DomainUnload += (obj, e) =>
10 {
11 Console.WriteLine("NewAppDomain unload!");
12 };
13 //調用委托
14 newAppDomain.DoCallBack(crossAppDomainDelegate);
15 AppDomain.Unload(newAppDomain) ;
16 Console.ReadKey();
17 }
18
19 static public void MyCallBack()
20 {
21 string name = AppDomain.CurrentDomain.FriendlyName;
22 for(int n=0;n<4;n++)
23 Console.WriteLine(string.Format( " Do work in {0}........" , name));
24 }

運行結果

 

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)
2 {
3 var message = string.Format(" CurrentThreadID is:{0}\tAppDomainID is:{1}",
4 Thread.CurrentThread.ManagedThreadId, AppDomain.CurrentDomain.Id);
5 Console.WriteLine(message);
6 Console.Read();
7 }

然后再新建一個ConsoleApplication項目,在此項目中新一個AppDomain對象,在新的AppDomain中通過ExecuteAssembly方法執行Example.exe程序。

 1         static void Main(string[] args)
2 {
3 //當前應用程序域信息
4 Console.WriteLine("CurrentAppDomain start!");
5 ShowMessage();
6
7 //建立新的應用程序域對象
8 AppDomain newAppDomain = AppDomain.CreateDomain("newAppDomain");
9 //在新的應用程序域中執行Example.exe
10 newAppDomain.ExecuteAssembly("Example.exe");
11
12 AppDomain.Unload(newAppDomain);
13 Console.ReadKey();
14 }
15
16 public static void ShowMessage()
17 {
18 var message = string.Format(" CurrentThreadID is:{0}\tAppDomainID is:{1}",
19 Thread.CurrentThread.ManagedThreadId, AppDomain.CurrentDomain.Id);
20 Console.WriteLine(message);
21 }

運行結果

可見,ID等於9的線程在不同時間內分別運行於AppDomain 1與AppDomain 2當中。

 

4.3 跨上下文的線程

線程既然能夠跨越AppDomain的邊界,當然也能跨越不同的上下文。
下面這個例子中,線程將同時運行在默認上下文與提供安全線程的上下文中。

 1     class Program
2 {
3 [Synchronization]
4 public class ContextBound : ContextBoundObject
5 {
6 public void Test()
7 {
8 ShowMessage();
9 }
10 }
11
12 static void Main(string[] args)
13 {
14 //當前應用程序域信息
15 Console.WriteLine("CurrentAppDomain start!");
16 ShowMessage();
17
18 //在上下文綁定對象中運行線程
19 ContextBound contextBound = new ContextBound();
20 contextBound.Test();
21 Console.ReadKey();
22 }
23
24 public static void ShowMessage()
25 {
26 var message = string.Format(" CurrentThreadID is:{0}\tContextID is:{1}",
27 Thread.CurrentThread.ManagedThreadId, Thread.CurrentContext.ContextID);
28 Console.WriteLine(message);
29 }
30 }

運行結果

 

本篇總結

進程(Process)、線程(Thread)、應用程序域(AppDomain)、上下文(Context)的關系如圖5.0,一個進程內可以包括多個應用程序域,也有包括多個線程,線程也可以穿梭於多個應用程序域當中。但在同一個時刻,線程只會處於一個應用程序域內。線程也能穿梭於多個上下文當中,進行對象的調用。

雖然進程、應用程序域與上下文在平常的開發中並非經常用到,但深入地了解三者的關系,熟悉其操作方式對合理利用系統的資源,提高系統的效率是非常有意義的。
尤其是三者與線程之間的關系尤為重要,特別是在一個多線程系統中,如果不能理清其關系而盲目使用多線程,容易造成資源搶占與死鎖之類的錯誤。

 圖5.0

希望本篇文章對相關的開發人員有所幫助。
對 .NET 開發有興趣的朋友歡迎加入QQ群:230564952 共同探討 !

回到目錄

C#綜合揭秘

通過修改注冊表建立Windows自定義協議
Entity Framework 並發處理詳解

細說進程、應用程序域與上下文

細說多線程(上)

細說多線程(下)
細說事務
深入分析委托與事件

 

作者:風塵浪子
http://www.cnblogs.com/leslies2/archive/2012/03/06/2379235.html 

原創作品,轉載時請注明作者及出處





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM