程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 【插件式框架探索系列】應用程序域(AppDomain)

【插件式框架探索系列】應用程序域(AppDomain)

編輯:關於.NET

應用程序域(AppDomain)已經不是一個新名詞了,只要熟悉.net的都知道它的存在,不過我們還是先一起來重新認識下應用程序域吧,究竟它是何方神聖。

應用程序域

眾所周知,進程是代碼執行和資源分配的最小單元,每個進程都擁有獨立的內存單元,而進程之間又是相互隔離的,自然而然,進程成為了代碼執行的安全邊界。

一個進程對應一個應用程序是一個普遍的認知,而.net卻打破了這一慣例,因為它帶來了應用程序域這一全新的概念,CLR可使用應用程序域來提供應用程序之間的隔離,而一個進程中可以運行多個應用程序域,也就是說只要使用應用程序域,我們可以在一個進程中運行多個應用程序,而不會造成進程間調用或進程間切換等方面的額外開銷。

是不是覺得應用程序域是個很神奇的東東了,別急,我們再來看看它的隔離特性又為我們帶來了什麼。

優勢

首先,應用程序域之間是不相互影響的,它是天生的異常隔離機制。也就是說,在一個應用程序域中出現的錯誤不會影響到其他應用程序域,因為類型安全的代碼不會導致內存錯誤。

其次,它能夠在運行時動態的加載和卸載程序集。我們都知道,在.net世界中,加載器一旦加載了程序集,那麼它將一直存在於應用程序的整個生命周期中,而應用程序域則改變了這一切,它為我們提供了卸載程序集的能力。

最後,應用程序域可以單獨實施安全策略和配置策略。說白了就是可以為每個應用程序域配置相應的權限,以更好的管理應用程序。

另外值得注意的是,應用程序域和線程之間不具有一對一的相關性。在任意給定時間,在單個應用程序域中可以執行多個線程,而特定線程並不局限在單個應用程序域內。也就是說,線程可以自由跨越應用程序域邊界,如果沒有主動新啟線程,那麼多個應用程序域依然運行在同一個線程中。

總的來說,應用程序域形成了托管代碼的隔離、卸載和安全邊界。而這些特性帶給一個插件式框架的將是異常隔離、動態加載卸載插件和更安全的插件運行環境。

由於這篇文章的定位是針對框架設計結合應用程序域的特性,因此假設你已經對應用程序域有了一定的了解了,下面通過示例,讓我們一步一步來認識應用程序域的這些特性。

創建和卸載AppDomain

使用C#我們可以用如下的方式創建一個應用程序域,並在新域中執行一段代碼:

AppDomain domain = AppDomain.CreateDomain("Hello AppDomain!");
       domain.DoCallBack(new CrossAppDomainDelegate(() =>
       {
         Window win = new Window
         {
           Width = 300,
           Height = 100,
           Content = AppDomain.CurrentDomain.FriendlyName
         };
         win.Show();
       }));

運行後可以看到在新域中創建的Window展示如下:

卸載應用程序域則可以通過AppDomain靜態方法AppDomain.Unload(domain)實現,就是這麼簡單。

配置域加載方式

如果你運行了上面這段代碼,是不是發現新域創建的Window過了好久才呈現出來,這是怎麼回事呢,簡單來說,這是因為.net加載器默認的行為是在每個域裡都會重新加載引用的程序集(包括Framework本身除了mscorlib外的程序集),當然我們可以更改這種行為,不過在這之前我們先來了解下一個新概念”domain neutrality”, 詳細資料可以看這篇文章Domain Neutral Assemblies,簡單來說它擁有跨域共享程序集的能力,這就避免了重復加載的損耗,我們可以通過為程序入口main函數添加LoaderOptimization標簽修改默認加載方式:

[System.STAThreadAttribute()]
     [System.Diagnostics.DebuggerNonUserCodeAttribute()]
     [LoaderOptimization(
     LoaderOptimization.MultiDomainHost)]
     public static void Main()
     {
       AppDomainTest.App app = new AppDomainTest.App();
       app.InitializeComponent();
       app.Run();
     }

重新編譯運行,速度有了明顯的提升吧。

LoaderOptimization有三種方式(SingleDomain, MultiDomain和MultiDomainHost),在Domain Neutral Assemblies中均有詳細的解譯,有興趣的朋友可以看下,此處就不再重述了。

異常隔離

對於插件式框架而言,異常隔離是非常重要的,這是保證一個框架穩定性的必要特性。下面我們來看看使用應用程序域如何實現異常隔離。

首先我們來模擬在新創建的域中拋出異常:

AppDomain domain = AppDomain.CreateDomain("Hello AppDomain!");
       domain.DoCallBack(new CrossAppDomainDelegate(() =>
       {
         Window win = new Window
         {
           Width = 300,
           Height = 100,
           Content = AppDomain.CurrentDomain.FriendlyName
         };
         win.Loaded += (obj, arg) =>
         {
           throw new Exception("test exception.");
         };
         win.Show();
       }));

這裡采用的是在Window loaded事件中直接拋出異常達到模擬效果,OK,編譯運行,很不幸,成功掛掉。

這是因為新域中未處理的異常,最終都會拋至默認域,進而導致崩潰。要驗證這一點,很容易,我們只要在默認域中添加 AppDomain.CurrentDomain.UnhandledException事件處理就可以截獲到新域中拋出的異常,可惜在此你只能截獲卻無法改變崩潰的結果。

那麼如何才能處理掉這個異常,在默認域或者新域中注冊System.Windows.Threading.Dispatcher.CurrentDispatcher.UnhandledException事件處理,示例如下:

AppDomain domain = AppDomain.CreateDomain("Hello AppDomain!");
System.Windows.Threading.Dispatcher.CurrentDispatcher.UnhandledException += (obj, arg) =>
{
   arg.Handled = true;
   MessageBox.Show(arg.Exception.Message);
   AppDomain.Unload(domain);
};
domain.DoCallBack(new CrossAppDomainDelegate(() =>
{
   Window win = new Window
   {
     Width = 300,
     Height = 100,
     Content = AppDomain.CurrentDomain.FriendlyName
   };
   win.Loaded += (obj, arg) =>
   {
     throw new Exception("test exception.");
   };
   win.Show();
}));

注意到最關鍵的arg.Handled = true這一句,它的意義在於告訴系統這個事件已經被處理過了,不要再往下傳遞了,最後主動把新域卸載掉,而默認域則仍然正常運行著,如此便達到了異常隔離的效果。

組合不同域中的插件

假設所有的插件都處於不同的域中,那麼如何組合它們呢,即如何將不同域中的插件同時呈現到一個容器中。

眾所周知,要實現對象在域之間傳遞,對象必須是可序列化的或者是繼承自MarshalByRefObject的類型,然而UI控件對此卻是無能為力了, 在此就需要微軟的Addin框架幫助了,雖然大家都覺得Addin框架復雜、難用,但是裡面有好些東西還是很有用處的,比如這裡將要用到的 FrameworkElementAdapters類,它提供了兩個靜態方法ContractToViewAdapter和ViewToContractAdapter用於實現FrameworkElement和INativeHandleContract之間的相互轉換,傳說中這種轉換是通過句柄實現的。還是用例子來說明如何讓插件跨域呈現吧,首先添加System.Addin.Contract.dll和System.Windows.Presentation.dll兩個引用,然後編寫如下代碼

AppDomain domain = AppDomain.CreateDomain("test");
domain.DoCallBack(new CrossAppDomainDelegate(() =>
{
   // 在新域中創建Button控件
   Button btn = new Button { Content = "test" };
   // 將Button控件轉換為INativeHandleContract
   INativeHandleContract ict = FrameworkElementAdapters.ViewToContractAdapter(btn);
   AppDomain.CurrentDomain.SetData("testbtn", ict);
}));
// 在主域中獲取新域中的INativeHandleContract對象
INativeHandleContract iContract = domain.GetData("testbtn") as INativeHandleContract;
// 將INativeHandleContract對象轉換回FrameworkElement
FrameworkElement ctrl = FrameworkElementAdapters.ContractToViewAdapter(iContract);
Application.Current.MainWindow.Content = ctrl;

運行結果如下,新域中創建的控件成功的呈現在了主域中

至於域中的權限配置部分,將在下篇中講述。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved