C#:進程、線程、應用程序域(AppDomain)與上下文分析
進程
進程是操作系統用於隔離眾多正在運行的應用程序的機制。在.Net之前,每一個應用程序被加載到單獨的進程中,並為該進程指定私有的虛擬內存。進程不能直接訪問物理內存,操作系統通過其它的處理把這些虛擬內存映射到物理內存或IO設備的某個區域,而這些物理內存之間不會有重疊,這就決定了一個進程不可能訪問分配給另一個進程的內存。相應地,運行在該進程中的應用程序也不可能寫入另一個應用程序的內存,這確保了任何執行出錯的代碼不會損害其地址空間以外的應用程序。在這種機制下,進程作為應用程序之間一個獨立而安全的邊界在很大程度上提高了運行安全。
進程的缺點是降低了性能。許多一起工作的進程需要相互通信,而進程卻不能共享任何內存,你不能通過任何有意義的方式使用從一個進程傳遞到另一個進程的內存指針。此外,你不能在兩個進程間進行直接調用。你必須代之以使用代理,它提供一定程度的間接性。雖然,使用動態連接庫dll讓所有的組件運行在同一空間,一定程度上可以提高性能,但這些組件相互影響,一個組件的錯誤將極有可能導致整個應用程序的崩潰,“dll地獄”更是讓許多應用程序難以避免。
線程
線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程自己不擁有系統資源,只擁有一點在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。一個線程可以創建和撤消另一個線程,同一進程中的多個線程之間可以並發執行。由於線程之間的相互制約,致使線程在運行中呈現出間斷性。線程也有就緒、阻塞和運行三種基本狀態。
線程是程序中一個單一的順序控制流程.在單個程序中同時運行多個線程完成不同的工作,稱為多線程.
線程和進程的區別在於,子進程和父進程有不同的代碼和數據空間,而多個線程則共享數據空間,每個線程有自己的執行堆棧和程序計數器為其執行上下文.多線程主要是為了節約CPU時間,發揮利用,根據具體情況而定. 線程的運行中需要使用計算機的內存資源和CPU.
應用程序域(AppDomain)
在.Net中,應用程序有了一個新的邊界:應用程序域(以下簡稱域)。它是一個用於隔離應用程序的虛擬邊界。為了禁止不應交互的代碼進行交互,這種隔離是必要的。.Net的應用程序在域層次上進行隔離,一個域中的應用程序不能直接訪問另一個域中的代碼和數據。這種隔離使得在一個應用程序範圍內創建的所有對象都在一個域內創建,確保在同一進程中一個域內運行的代碼不會影響其他域內的應用程序,大大提高了運行的安全。
.Net結構中,由於公共語言運行庫能夠驗證代碼是否為類型安全的代碼,所以它可以提供與進程邊界一樣大的隔離級別,其性能開銷也要低得多。你可以在單個進程中運行幾個域,而不會造成進程間調用或切換等方面的額外開銷。這種方法是把任何一個進程分解到多個域中,允許多個應用程序在同一進程中運行,每個域大致對應一個應用程序,運行的每個線程都在一個特殊的域中。如果不同的可執行文件都運行在同一個進程空間中,它們就能輕松地共享數據或直接訪問彼此的數據。這種代碼同運行同一個進程但域不同的類型安全代碼一起運行時是安全的。在一個進程內運行多個應用程序的能力顯著增強了服務器的可伸縮性。
域與線程的關系
在.Net中,線程是公共語言運行庫用來執行代碼的操作系統構造。在運行時,所有托管代碼均加載到一個域中,由特定的操作系統線程來運行。然而,域和線程之間並不具有一一對應關系。在任意給定時間,單個域中可以執行不止一個線程,而且特定線程也並不局限在單個域內。也就是說,線程可以跨越域邊界,不為每個域創建新線程。當然,在指定時刻,每一線程都只能在一個域中執行。運行庫會跟蹤所有域中有哪些線程正在運行。通過調用.Net類庫的 Thread.GetDomain 方法,你還可以確定正在執行的線程所在的域。
一、進程的概念與作用
二、應用程序域
三、深入了解.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
C#:進程、線程、應用程序域(AppDomain)與上下文分析