← 筆記

C#筆記 – AppDomain(應用域)

AppDomain
  • AppDomain是一組程序集的邏輯容器
    • CLR COM服務器初始化時會創建一個AppDomain(默認AppDomain),進程終止時銷毀
    • 目的是為了提供「隔離」
  • 具體功能
    • 一個AppDomain的代碼不能直接訪問另一個AppDomain的代碼創建的對象

      • 對象的生存期不能超過創建它的代碼所在的AppDomain
      • 一個AppDomain要訪問另一個AppDomain的對象:
        • 「按引用封送」

          •   //按引用封送
              AppDomain ad = AppDomain.CreateDomain("AD #1");
              //切換至新Domain,然後在元數據中查找類型MarshalByRefType並構造
              //返回的是一個「代理」,而不是真實對象
              MarshalByRefType mbrt = (MarshalByRefType)ad
                  .CreateInstanceAndUnwrap(Assembly.GetEntryAssembly().FullName, "MarshalByRefType");
              mbrt.SomeMethod();
              AppDomain.Unload(ad);
              
              public class MarshalByRefType : MarshalByRefObject //繼承自MarshalByRefObject
              {
                  public MarshalByRefType() { }
              
                  public void SomeMethod() { }
              
                  public MarshalByValType MethodWithReturn()
                  {
                      return new MarshalByValType();
                  }        
              }
        • 「按值封送」

          •   //按值封送
              ad = AppDomain.CreateDomain("AD #2");
              //將對象序列化,加載到目標Domain後再反序列化
              //返回副本的引用
              mbrt = (MarshalByRefType)ad
                  .CreateInstanceAndUnwrap(Assembly.GetEntryAssembly().FullName, "MarshalByRefType");
              MarshalByValType mbvt = mbrt.MethodWithReturn();
              mbvt.OtherMethod();
              AppDomain.Unload(ad);
              
              [System.Serializable]
              public class MarshalByValType //可序列化
              {
                  public void OtherMethod() { }
              }
    • AppDomain可以卸載

      • CLR不支持從AppDomain中卸載特定的程序集,但是可以讓CLR卸載一個AppDomain
    • AppDomain可以單獨保護

    • AppDomain可以單獨配置

卸載AppDomain
  • Step
    • CLR掛起進程中執行過托管代碼的所有線程
    • CLR檢查所有線程棧,查看哪些線程正在執行要卸載的AppDomain中的內容
      • 導致線程展開
      • 執行所有finally塊
    • 確定所有線程離開AppDomain後,CLR遍歷堆,為引用了「由已卸載」AppDomain創建的對象的每個代理對象設置一個標志
      • 表示它們引用的真實對象已經不在了,成為了「無效代理對象」
    • CLR強制GC
    • 恢復剩餘進程執行
監視AppDomain
  • 一旦開啟就不能關閉
  • 接口:
    • MonitoringSurvivedProcessMemorySize
      • 返回由當前CLR實例控制的所有AppDomain使用的字節數
    • MonitoringTotalAllocatedMemorySize
      • 返回特定AppDomain已分配字節數
    • MonitoringSurvivedMemorySize
      • 返回特定AppDomain當前正在使用的字節數
    • MonitoringTotalProcessorTime
      • 返回特定AppDomain的CPU占用率
    • 返回字節數的接口只保證在上一次GC時是準確的
Windows進程中的AppDomain與CLR

  • Windows進程運行著一個CLR COM服務器(執行引擎)
    • CLR管理著1個或以上的AppDomain - 每個AppDomain有自己的Loader堆 - 每個Loader堆記錄了自AppDomain創建以來已訪問過的類型 - Loader堆中每個類型對象(Type1/Type2)都有一個方法表 - 方法表中的每個記錄項都指向JIT編譯的本機代碼(方法至少執行過1次) - 每個AppDomain加載了一些程序集 - 如果兩個AppDomain都加載了同一個程序集(如圖中的System.dll),並使用了來自System.dll的一個類型,那麼兩個AppDomain的Loader堆會為相同的類型分別分配一個類型對象 - 類型對象的內存不會由兩個AppDomain共享 - 而調用方法時的IL代碼經JIT編譯後,生成的本機代碼也單獨與各自的AppDomain關聯 - AppDomain中立 - 有的程序集本來就要由多個AppDomain使用,如MSCorLib.dll - CLR初始化時,會自動加載該程序集,所有AppDomain共享該程序集中的類型 - 為減少資源消耗,該程序集會以一種「AppDomain中立」的方式加載 - CLR會為AppDomain中立方式加載的程序集維護一個特殊的Loader堆 - 該Loader堆中的所有類型對象、類型定義的方法JIT編譯生成的本機代碼都會由進程中的所有AppDomain共享 - 這些程序集無法卸載,只有在終止Windows進程時由Windows去回收資源
可執行應用程序(exe)對AppDomain的使用
  • 起動
    • Windows用托管exe文件初始化進程時,會加載「墊片」(MSCorEE.dll)
    • 「墊片」檢查程序集的CLR頭信息,決定加載哪個版本的CLR到進程
    • CLR加載後,CLR頭再次被檢查,查找並執行應用程序的入口方法
  • 運行
    • 代碼運行時會訪問其他類型,引用另一個程序集的類型時,CLR定位所需的程序集,將其加載到同一個AppDomain中
    • 入口方法返回後,Windows進程終止

參考書目

  • 《CLR via C#》(第4版) Jeffrey Richter