前一個項目,有機會學了AppDomian基本概念。把學習心得寫下來。範例可以在這裏下載
AppDomain的作用
AppDomain 也叫應用程序域,是CLR創建的,用來規範代碼的執行範圍,並提供獨立的錯誤隔離機制。因此,每個應用程序包可以包含一個或多個AppDomain,每個AppDomain能夠獨立卸載及管理Assembly。
因爲AppDomain也是獨立存在互不影響,而且創建一個AppDomain和跨AppDomain的開銷都比進程要低得多,所以常常使用獨立AppDomain來提供服務,避免由於程序某部分出錯而導致整個程序崩潰。
示例簡述
通過調用不同AppDomain中SayHi的對象 實現了 中文,英文,同時使用中英文的SayHi方法,以及異常問題。
- MultiAppDomain 項目:是默認程序域的所在項目。用於創建副AppDomain,並且調用副AppDomain裏面的服務實現SayHi的調用
- Interface項目。只有一個接口,用於規範SayHi的調用方式。
- SayHiChinese::實現中文SayHi
- SayHiEnglish: 實現英文SayHI
- SayHiBoth:調用副AppDomain中的實現中英文SayHi
- SayHiException:在條用過程中拋出一個異常,並且檢查副AppDomain中的狀態。
AppDomain的基本方法
默認AppDomain就是在應用程序一開始運行所在的程序域。而副AppDomain指的就是在默認程序域或者其他程序域下創建的程序域。
- 創建一個AppDomain。
無論在程序域中或者副AppDoman中都可以創建新的AppDomain,而這個程序域就是受到當前程序域管理的。如:
輸入參數味AppDomain的名稱,這個名稱可以重複。也就是你可以有多個叫做chinese的的程序域
默認地 chineseAppDomain. BaseDirectory指向的目錄是默認程序域的目錄。當程序運行的時候,需要元數據,會後首先從這個目錄開始查找。
- 卸載一個程序域。
默認程序域是不能被卸載的,否則回拋出CannotUnloadAppDomainException 錯誤。
如果是副程序域自己卸載自己,哪麼拋出內部拋出System.Threading.ThreadAbortException 異常,但是在外部可以捕獲System.AppDomainUnloadedException。
- 獲取當前的AppDomain
如果當前在默認程序域中,哪麼就獲取默認程序域對象,如果在其他副AppDomain中,哪麼就獲取各自的AppDomain。
創建第一個例子。在項目MultiAppDomain,打開文件Program.cs ,在Main 函數中找到
AppDomain englishAppDomain = AppDomain.CreateDomain("english");
AppDomain bothAppDomain = AppDomain.CreateDomain("both");
AppDomain exceptionDomain = AppDomain.CreateDomain("exception");
- AppDomain之間值傳遞
只有繼承了System.MarshalbyRefObject或者聲明屬性SerializableAttribute的類的實例,才能在AppDomain之間傳遞。即:
Public class A
{}
Public Class B:System.MarshalbyRefObject
{}
假設 A的實例a 和 B的實例b,需要從AppDomain X傳遞到AppDomain Y中。哪麼 a實例相當於對象拷貝。在 AppDomain Y中任何對 a的操作都不會影響倒 AppDomainX中的a。而b是通過跨域的透明代理遞到,相當於值引用。因此 在 AppDomain Y中,任何對b的操作對於對 AppDomain X中的b產生影響。參考下圖:
在AppDoain中,可以通過AppDomain.SetData 和 GetData方法進行存取操作
AppDomain的動態導入
要實現在默認AppDomain中實現對副AppDomain的導入控制,必須獲取副AppDomain中的RemotLoader的實例如下圖
導入前,首先打開 MultiAppDomain項目中的RemoteLoader.cs 文件,查看一下Load方法。
{
private Dictionary<string, Assembly> assemblies = new Dictionary<string, Assembly>();
public void Load(string fullPath)
{
byte[] fsContent;
using(FileStream fs= File.OpenRead(fullPath))
{
fsContent = new byte[fs.Length];
fs.Read(fsContent, 0, fsContent.Length);
}
Assembly assembly = Assembly.Load(fsContent);
assemblies.Add(assembly.GetName().Name, assembly);
}
}
在Load方法內,使用byte導入Assembly,這樣做可以防止Assembly文件獨佔。如果使用文件路徑方式導入即Assembly.Load(filePath), 那麼文件在程序運行的過程中是不能被更改的。
在這裏說個題外話, 在寫這個例子的開始時,我是使用路徑導入方式,開始一直都很順利。當實現BothSayHi中SayHi的時候,卻拋出轉換異常的錯誤,說引用的透明代理(__TransparentProxy)對象不能被轉換成爲ISay接口類型。但是把代碼粘貼在Main方法中就正常。後來試過N種方法,包括改爲抽象類、類等都還是不行。正在百思不得其解,無意中把load導入改爲byte導入就正常了。
我個人認爲,是因爲在Main方法中,自身獨佔了Debug目錄下的Interface.dll。而其他副AppDomain是直接獲取Interface項目下的Dll。因此在Main方法中的調用是成功的,因爲元數據的所屬文件只有自己佔用。但是在BothSayHi中的Interface.dll是和其他副AppDomain一起使用的。因此在BothSayHi中,在轉換的時候,剛好需要獲取Interface的元數據,而interface剛好被其他副AppDomain佔用而無法讀取元數據。因此獲取不了元數據,只好拋出轉換異常。這個只是我個人的想法,希望有高手指出問題的本質。
好了言歸正傳 然後RemoteLoader需要在副AppDomain中創建,並在默認AppDomain引用。
在項目MultiAppDomain,文件Program.cs ,在Main 函數中找到:
(RemoteLoader) chineseAppDomain.CreateInstanceAndUnwrap("MultiAppDomain", "MultiAppDomain.RemoteLoader");
也可以寫成:
(RemoteLoader) chineseAppDomain.CreateInstance ("MultiAppDomain", "MultiAppDomain.RemoteLoader").Unwrap;
如果使用第二種方法創建,那麼返回的是System.Runtime.Remoting.ObjectHandle 對象。這個對象在Msdn的定義是:包裝按值封送對象引用,從而使它們可以通過間接尋址返回。因此在默認AppDomain中並不需要它的相關元數據(因此無需導入相關Assembly),直到使用方法unwarrp. 默認AppDomain纔開始導入相關的元數據。
Unwrap後對象是一個透明代理,再進行強制轉換。獲得RemoteLoader代理後,爲每個副域導入相關Assembly,如下:
{
string chineseDll = @"../../../SayHiChinese/bin/Debug/SayHiChinese.dll";
string interfaceDll = @"../../../Interface/bin/Debug/Interface.dll";
//省略代碼
private static void Main(string[] args)
{
//省略代碼
chineseLoader.Load(interfaceDll);
chineseLoader.Load(chineseDll);
chineseLoader.CreateService("sayHi", "SayHiChinese", "SayHiChinese.SayHi");
//省略代碼
}
}
留意下chineseLoader.Load(interfaceDll),這句是可以省略的(開始不明原因,興奮了好一陣子,以爲.net有這麼高的只能)。首先,在MultiAppDomain中所在的默認目錄已經存在這個文件(因爲項目顯示引用了Interface項目),然後在創建chineseAppDomain的時候,我並沒有指定副AppDomain的BaseDirectory,因此BaseDirecotry默認指向MultiAppDomain的運行目錄。而在這MultiAppDomain項目中,我們引用了Interface,因此在路徑中也包含了Interface.Dll,所以當我們調用chineseLoader.CreateService()創建SayHiChinese.SayHi實例時,CLR自動從MultiAppDomain目錄中,自動導入Interface.Dll。
因爲BothSayHi是需要其他兩個實例的支持才能完成,因此創建代碼就需要輸入chinese和English的實例的
bothLoader.Load(bothDll);
bothLoader.SetServiceFrom("english", englishLoader.GetService("sayHi"));
bothLoader.CreateService("sayHi", "BothSayHi", "BothSayHi.SayHi");
在BothSayHi中實現調用chinese和english的服務:
bothSayHi的方法
{
ISayHi chinese=(ISayHi)AppDomain.CurrentDomain.GetData("chinese");
ISayHi english = (ISayHi) AppDomain.CurrentDomain.GetData("english");
Console.WriteLine(chinese.Say() + " " + english.Say());
return chinese.Say() + " " + english.Say();
}