1. 程式人生 > >.NET AppDomain

.NET AppDomain

.NET AppDomain

作業系統啟動託管程式後,會呼叫 CLR 來託管該程式,CLR 在初始化時會預設建立一個 AppDomain 來執行託管程式碼。AppDomain 是為了隔離而設計的,它將程式集限定在某個域中執行,而不影響其它域的狀態,它可以極大地提高託管程式的穩定性:

  • 支援動態解除安裝程式集:在外掛架構中,不必等到程序結束時才釋放,避免不必要的記憶體佔用
  • 實現了程式集隔離:將一些容易引起崩潰的程式碼單獨執行在一個 AppDomain 中
  • 限制程式碼安全許可權:對不同的 AppDomain 應用不同的安全級別
  • 載入相同程式集的不同版本:在同一個程序中使用多個 AppDomain 載入不同版本的兩個程式集

AppDomain 特徵

AppDomain 可理解為 .NET 程序,與執行緒相比,它是一個靜態的概念。

  • 一個 AppDomain 中的程式碼建立的物件不能由另一個 AppDomain 中的程式碼直接訪問
  • AppDomain 可以單獨解除安裝:解除安裝當前 AppDomain 中的所有程式集
  • AppDomain 可以單獨保護:保證不會破壞宿主本身的重要資料結構
  • AppDomain 可以單獨實施配置:載入方式(搜尋路徑、版本繫結重定向、載入器優化等)

上圖展示了一個 Windows 程序,其中寄宿了 CLR 和兩個 AppDomain 。每個 AppDomain 都會在各自的 Loader Heap 中獨立地載入相關的程式集,都會獨立地維護型別的靜態欄位。如果 CLR 要求解除安裝某個 AppDomain 並釋放其所有資源時,不會影響到其它 AppDomain 的執行,這便是 AppDomain 的隔離效果。

對於一些基礎型別的程式集,比如 MSCoreLib.dll(其中包含了 System.Object、System.Int32 以及其它 .NET Framework 密不可分的型別),CLR 採用了 “AppDomain 中立” 的方式載入,所有 AppDomain 都共享,以減少資源消耗。

跨越 AppDomain 邊界訪問物件

1. 按引用封送(Marshal-by-Reference)

public class MarshalByRefType : MarshalByRefObject
{
    public void SomeMethod()
    {
        Console.
WriteLine($"Current AppDomain: {AppDomain.CurrentDomain.FriendlyName}"); } } class Program { static void Main(string[] args) { Console.WriteLine($"Default AppDomain : {AppDomain.CurrentDomain.FriendlyName}"); // 新建一個 AppDomain(安全性和配置匹配於當前 AppDomain) AppDomain newDomain = AppDomain.CreateDomain("New AppDomain", null, null); // 將程式級載入到新的 AppDomain 中,並封送回原 AppDomain 中(實際得到的是一個代理物件的引用) MarshalByRefType refType = (MarshalByRefType)newDomain.CreateInstanceAndUnwrap("AppDomainDemo", "AppDomainDemo.MarshalByRefType"); // 看一下型別,CLR 在撒謊 Console.WriteLine($"Type : {refType.GetType()}"); // 證明是一個代理物件的引用 Console.WriteLine($"IsTransparentProxy : {RemotingServices.IsTransparentProxy(refType)}"); // 實際是在 New AppDomain 中呼叫的方法 refType.SomeMethod(); // 解除安裝 New AppDomain,代理物件將引用一個無效的 AppDomain AppDomain.Unload(newDomain); try { refType.SomeMethod(); Console.WriteLine("Successful call..."); } catch (AppDomainUnloadedException) { Console.WriteLine("Failed call..."); } // Wait to exit... Console.ReadKey(); } }
Default AppDomain : AppDomainDemo.exe
Type : AppDomainDemo.MarshalByRefType
IsTransparentProxy : True
Current AppDomain: New AppDomain
Failed call...

代理物件的首次租期為 5分鐘,如果 5 分鐘內未被使用,將被回收。首次使用後,租期變為 2 分鐘,即連續 2 分鐘為被呼叫,將被回收。

2. 按值封送(Marshal-by-Reference)

[Serializable]
public class MarshalByValType
{
    public void SomeMethod()
    {
        Console.WriteLine($"Current AppDomain: {AppDomain.CurrentDomain.FriendlyName}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine($"Default AppDomain : {AppDomain.CurrentDomain.FriendlyName}");

        // 新建一個 AppDomain(安全性和配置匹配於當前 AppDomain)
        AppDomain domain = AppDomain.CreateDomain("New AppDomain", null, null);

        MarshalByValType person = (MarshalByValType)domain.CreateInstanceAndUnwrap("AppDomainDemo", "AppDomainDemo.MarshalByValType");
        Console.WriteLine($"IsTransparentProxy : {RemotingServices.IsTransparentProxy(person)}");

        person.SomeMethod();
        AppDomain.Unload(domain);

        try
        {
            person.SomeMethod();
            Console.WriteLine("Successful call...");
        }
        catch (AppDomainUnloadedException)
        {
            Console.WriteLine("Failed call...");
        }

        // Wait to exit...
        Console.ReadKey();
    }
}
Default AppDomain : AppDomainDemo.exe
IsTransparentProxy : False
Current AppDomain: AppDomainDemo.exe
Current AppDomain: AppDomainDemo.exe
Successful call...

如果某個物件既未繼承自 MarshalByRefObject,也未標記 SerializableAttribute ,則在跨域訪問時將丟擲 SerializationException 異常。

AppDomain 解除安裝

Assembly 未提供解除安裝程式集的方法,只能通過 AppDomain 來解除安裝。

可使用 AppDomain.Unload() 靜態方法來解除安裝某個 AppDomain ,從而解除安裝程式集。其提供 10 秒鐘時間讓執行緒從該 AppDomain 離開,具體步驟如下:

  1. 掛起程序中執行過託管程式碼的所有執行緒
  2. 檢查所有執行緒,對與該 AppDomain 相關的執行緒丟擲一個 ThreadAbortException
  3. 對所有引用解除安裝應用程式域裡物件的代理物件設定一個標識(引發 AppDomainUnloadedException)
  4. 垃圾回收器會回收所有要解除安裝應用程式域裡的物件
  5. CLR 恢復所有執行緒的執行

AppDomain 監視與異常通知

  • 監視
    • MonitoringSurvivedProcessMemorySize:當前 CLR 例項中所有 AppDomain 正在使用的位元組數
    • MonitoringTotalAllocatedMemorySize:特定 AppDomain 已分配的位元組數
    • MonitorigSurvivedMemorySize:特定 AppDomain 正在使用的位元組數
    • MonitoringTotalProcessorTime:特定的 AppDomain 的 CPU 佔用率
  • 異常通知
    • AppDomain.CurrentDomain.UnhandledException
    • AppDomain.CurrentDomain.FirstChanceException

AppDomain 應用

  • Silverlight 富 Internet 應用程式:每個 Silverlight 控制元件就是一個 AppDomain
  • ASP.NET Web 窗體應用程式:不關閉 Web 伺服器,動態更改程式碼
  • MySQL Server:儲存過程在自己的安全 AppDomain 中執行

參考資料