22-Bob: CLR Hosting va AppDomains

CLR ni hosting qilish, AppDomainlar, izolyatsiya, AppDomain yuklash va tushirish, monitoring, va ilg'or host boshqaruvi

Ushbu bobda men Microsoft .NET Framework tomonidan taqdim etiladigan ajoyib qiymatni ko'rsatadigan ikkita asosiy mavzuni muhokama qilaman: hosting va AppDomainlar. Hosting ixtiyoriy ilovaga umumiy til ishlash muhiti (CLR) xususiyatlaridan foydalanish imkonini beradi. Xususan, bu mavjud ilovalarga kamida qisman boshqariluvchi kodda yozilish imkoniyatini beradi. Bundan tashqari, hosting ilovalarga dasturlash orqali sozlash va kengaytirishni taklif qiladi.

Kengaytiruvchanlikka ruxsat berish uchinchi tomon kodining sizning jarayoningiz ichida ishlashini anglatadi. Windows'da uchinchi tomon DLL'larini jarayonga yuklash doimo xavfli bo'lgan. DLL ilovaning ma'lumot tuzilmalari va kodini osongina buzishi mumkin. DLL shuningdek ilova xavfsizlik kontekstidan foydalanib, kirish huquqi bo'lmasligi kerak bo'lgan resurslarga kirishga urinishi mumkin. CLR ning AppDomain xususiyati bu muammolarning barchasini hal qiladi. AppDomainlar uchinchi tomon ishonchsiz kodni mavjud jarayonda ishga tushirishga ruxsat beradi va CLR ma'lumot tuzilmalari, kod va xavfsizlik kontekstining buzilmasligini kafolatlaydi.

Dasturchilar odatda hosting va AppDomainlarni assembly yuklash va reflection bilan birga ishlatadi. Ushbu to'rt texnologiyani birgalikda qo'llash CLR ni nihoyatda boy va kuchli platformaga aylantiradi. Ushbu bobda men hosting va AppDomainlarga to'xtalaman. Keyingi bobda esa assembly yuklash va reflectionga e'tibor qarataman.

CLR Hosting

.NET Framework Windows ustida ishlaydi. Bu .NET Framework Windows interfeys qila oladigan texnologiyalar yordamida qurilishi kerakligini anglatadi. Avvalo, barcha boshqariluvchi modul va assembly fayllari Windows portativ bajariladigan (PE) fayl formatidan foydalanishi va Windows bajariladigan (EXE) fayli yoki DLL bo'lishi kerak.

CLR ni ishlab chiqayotganda Microsoft uni DLL ichidagi COM server sifatida amalga oshirdi; ya'ni Microsoft CLR uchun standart COM interfeysi aniqladi va ushbu interfeysga GUID lar tayinladi. .NET Framework ni o'rnatganingizda CLR ni ifodalovchi COM server Windows registry da boshqa COM serverlar kabi ro'yxatga olinadi.

Ixtiyoriy Windows ilovasi CLR ni host qilishi mumkin. Biroq, siz CLR COM serverning instansiyasini CoCreateInstance chaqirish orqali yaratmasligingiz kerak; buning o'rniga boshqarilmagan hostingiz MetaHost.h da e'lon qilingan CLRCreateInstance funksiyasini chaqirishi kerak. CLRCreateInstance funksiyasi MSCorEE.dll faylida amalga oshirilgan bo'lib, u odatda C:\Windows\System32 katalogida joylashgan. Ushbu DLL shim (vositachi) deb ataladi va uning vazifasi CLR ning qaysi versiyasini yaratish kerakligini aniqlashdir; shim DLL CLR COM serverining o'zini o'z ichiga olmaydi.

Bitta mashinada CLR ning bir nechta versiyasi o'rnatilgan bo'lishi mumkin, lekin MSCorEE.dll faylining faqat bitta versiyasi bo'ladi. Mashinadagi MSCorEE.dll versiyasi mashinaga o'rnatilgan CLR ning eng so'nggi versiyasi bilan birga kelgan versiya hisoblanadi. Shuning uchun ushbu MSCorEE.dll versiyasi oldingi o'rnatilgan CLR versiyalarini qanday topishni biladi.

Haqiqiy CLR kodi har xil versiyalar uchun nomi o'zgargan faylda joylashgan. CLR ning 1.0, 1.1 va 2.0 versiyalari uchun CLR kodi MSCorWks.dll faylida, 4-versiya uchun esa Clr.dll faylida joylashgan. Bitta mashinada CLR ning bir nechta versiyasi bo'lishi mumkin bo'lgani uchun bu fayllar turli kataloglarga o'rnatiladi:

  • 1.0 versiyasi: C:\Windows\Microsoft.NET\Framework\v1.0.3705
  • 1.1 versiyasi: C:\Windows\Microsoft.NET\Framework\v1.0.4322
  • 2.0 versiyasi: C:\Windows\Microsoft.NET\Framework\v2.0.50727
  • 4 versiyasi: C:\Windows\Microsoft.NET\Framework\v4.0.21006

CLRCreateInstance funksiyasi boshqarilmagan ICLRMetaHost interfeysini qaytarishi mumkin. Host ilovasi ushbu interfeysning GetRuntime funksiyasini chaqirib, host yuklamoqchi bo'lgan CLR versiyasini belgilashi mumkin. Shim keyin CLR ning kerakli versiyasini host jarayoniga yuklaydi.

CLR Yuklash Mexanizmi

Odatda, boshqariluvchi bajariladigan fayl ishga tushganda, shim bajariladigan faylni tekshiradi va undan ilovaning qurilgan va sinovdan o'tkazilgan CLR versiyasini ko'rsatuvchi ma'lumotni oladi. Biroq, ilova o'zining XML konfiguratsiya faylida requiredRuntime va supportedRuntime yozuvlarini joylashtirish orqali ushbu standart xatti-harakatni bekor qilishi mumkin.

GetRuntime funksiyasi boshqarilmagan ICLRRuntimeInfo interfeysiga ko'rsatgich qaytaradi va undan GetInterface metodi orqali ICLRRuntimeHost interfeysi olinadi. Hosting ilovasi ushbu interfeys tomonidan aniqlangan metodlarni chaqirishi mumkin:

  • Host menejerlarini o'rnatish. CLR ga host xotira ajratish, oqimlarni rejalashtirish/sinxronlash, assembly yuklash va boshqa qarorlarni qabul qilishda ishtirok etishni xohlashini aytish. Host shuningdek axlat yig'ish qachon boshlanishi va tugashini va ma'lum operatsiyalar qachon tugashini bildirish xabarlarini olishni so'rashi mumkin.
  • CLR menejerlarini olish. CLR ga ba'zi sinflar/a'zolardan foydalanishni oldini olishni aytish. Bundan tashqari, host qaysi kodni nosozliklarni tuzatish mumkin va mumkin emasligini hamda maxsus hodisa — masalan, AppDomain tushirilishi, CLR to'xtashi yoki stek to'lib ketish istisnosi — yuz berganda hostdagi qaysi metodlarni chaqirish kerakligini aytishi mumkin.
  • CLR ni ishga tushirish.
  • Assembly yuklash va undagi kodni bajarish.
  • CLR ni to'xtatish, Windows jarayonida boshqariluvchi kodning ishlashini to'xtatish.
Eslatma

Albatta, Windows jarayoni CLR ni umuman yuklamasligi shart emas. Uni faqat jarayon ichida boshqariluvchi kodni bajarish kerak bo'lganda yuklash zarur. .NET Framework 4 dan oldin, CLR bitta Windows jarayoni ichida o'zining faqat bitta nusxasini joylashtirishga ruxsat berardi. Ya'ni, jarayonda CLR yo'q, yoki CLR ning v1.0, v1.1 yoki v2.0 versiyalaridan biri bo'lishi mumkin edi. Jarayonga faqat bitta CLR versiyasini ruxsat berish katta cheklov edi.

Biroq, .NET Framework 4 bilan Microsoft endi bitta Windows jarayonida v2.0 va v4.0 ni birga yuklash imkoniyatini qo'llab-quvvatlaydi, bu esa .NET Framework 2.0 va 4 versiyalari uchun yozilgan komponentlarga hech qanday moslik muammolarisiz yonma-yon ishlashga imkon beradi. Jarayonga qaysi CLR versiya(lar)i yuklanganligini ko'rish uchun ClrVer.exe vositasidan foydalanishingiz mumkin.

CLR Windows jarayoniga yuklangandan keyin uni hech qachon tushirib bo'lmaydi; ICLRRuntimeHost interfeysidagi AddRef va Release metodlarini chaqirish hech qanday ta'sir qilmaydi. CLR ni jarayondan tushirishning yagona yo'li jarayonni tugatishdir, bu Windows ga jarayon tomonidan ishlatilgan barcha resurslarni tozalashga olib keladi.

CLR ni Hosting Qilish Afzalliklari

CLR ni hosting qilish nima uchun foydali ekanligiga ko'plab sabablar bor. Hosting ixtiyoriy ilovaga CLR xususiyatlarini taklif qilish va kamida qisman boshqariluvchi kodda yozilish imkoniyatini beradi. Runtime ni hosting qiladigan har qanday ilova ilovani kengaytirishga harakat qilayotgan dasturchilarga ko'plab afzalliklarni taklif qiladi. Mana ba'zi afzalliklar:

  • Dasturlashni ixtiyoriy dasturlash tilida amalga oshirish mumkin.
  • Kod real vaqtda (JIT) kompilyatsiya qilinadi (interpretatsiya qilinishiga nisbatan tezlik uchun).
  • Kod xotira sizishlarini va buzilishlarini oldini olish uchun axlat yig'ishdan foydalanadi.
  • Kod xavfsiz sandbox ichida ishlaydi.
  • Hostga boy ishlab chiqish muhitini ta'minlash haqida qayg'urish kerak emas. Host mavjud texnologiyalardan foydalanadi: tillar, kompilyatorlar, muharrirlar, nosozlik tuzatuvchilar, profilerlar va boshqalar.

AppDomainlar

CLR COM serveri ishga tushganda u AppDomain yaratadi. AppDomain assemblylar to'plami uchun mantiqiy konteynerdir. CLR ishga tushganda yaratilgan birinchi AppDomain standart AppDomain deb ataladi; bu AppDomain faqat Windows jarayoni tugatilganda yo'q qilinadi.

Standart AppDomainga qo'shimcha ravishda host boshqarilmagan COM interfeys metodlari yoki boshqariluvchi tur metodlari yordamida CLR ga qo'shimcha AppDomainlar yaratishni buyurishi mumkin. AppDomain ning butun maqsadi izolyatsiyani ta'minlashdir. Mana AppDomain tomonidan taklif etiladigan maxsus xususiyatlar:

AppDomain Xususiyatlari

  • Bitta AppDomain dagi kod tomonidan yaratilgan obyektlarga boshqa AppDomain dagi kod to'g'ridan-to'g'ri murojaat qila olmaydi. AppDomain dagi kod obyekt yaratganda, ushbu obyekt o'sha AppDomain ga "tegishli" bo'ladi. Boshqacha aytganda, obyektning yashash muddati uni yaratgan AppDomain dan oshmasligi kerak. Boshqa AppDomain lardagi kod boshqa AppDomain ning obyektiga faqat marshal-by-reference yoki marshal-by-value semantikasi yordamida murojaat qilishi mumkin. Bu toza ajratish va chegarani ta'minlaydi, chunki bitta AppDomain dagi kod boshqa AppDomain da yaratilgan obyektga to'g'ridan-to'g'ri havola olishi mumkin emas. Bu izolyatsiya AppDomainlarni boshqa AppDomainlarda ishlaydigan kodga ta'sir qilmasdan jarayondan osongina tushirish imkonini beradi.
  • AppDomainlarni tushirish mumkin. CLR bitta assemblyni tushirishni qo'llab-quvvatlamaydi. Biroq, siz CLR ga AppDomain ni tushirishni aytishingiz mumkin, bu hozirda undagi barcha assemblylarni ham tushirishga olib keladi.
  • AppDomainlarni individual ravishda himoyalash mumkin. Yaratilganida AppDomain ga unda ishlaydigan assemblylarga beriladigan maksimal huquqlarni belgilaydigan ruxsat to'plami qo'llanishi mumkin. Bu hostga biror kodni yuklash va ushbu kod host tomonidan ishlatiladigan muhim ma'lumot tuzilmalarini buza olmasligini yoki o'qiy olmasligini ta'minlash imkonini beradi.
  • AppDomainlarni individual ravishda sozlash mumkin. Yaratilganida AppDomain ga bir qator konfiguratsiya sozlamalari bog'lanishi mumkin. Bu sozlamalar asosan CLR ning AppDomain ga assemblylarni qanday yuklashiga ta'sir qiladi. Qidiruv yo'llari, soya nusxalash, versiya bog'lash yo'naltirmalari va yuklagich optimizatsiyalariga oid konfiguratsiya sozlamalari mavjud.

Izolyatsiya va Jarayonlar

Muhim

Windows ning ajoyib xususiyatlaridan biri shundaki, u har bir ilovani o'z jarayon manzil maydoni ichida ishga tushiradi. Bu bitta ilovadagi kodning boshqa ilova tomonidan ishlatiladigan kodni yoki ma'lumotlarga kirish yoki buzish mumkin emasligini ta'minlaydi. Jarayonlarni izolyatsiya qilish xavfsizlik teshiklari, ma'lumotlar buzilishi va boshqa oldindan aytib bo'lmaydigan xatti-harakatlarning oldini oladi, Windows va undagi ilovalarni ishonchli va mustahkam qiladi. Afsuski, Windows da jarayonlarni yaratish juda qimmat. Win32 ning CreateProcess funksiyasi juda sekin va Windows jarayonning manzil maydonini virtuallash uchun ko'p xotira talab qiladi.

Biroq, agar ilova butunlay tekshiriladigan xavfsiz boshqariluvchi koddan iborat bo'lsa va boshqarilmagan kodga chaqiriq qilmasa, bitta Windows jarayonida bir nechta boshqariluvchi ilovalarni ishga tushirish bilan bog'liq hech qanday muammo yo'q. AppDomainlar ushbu ilovalarning har birini himoyalash, sozlash va tugatish uchun zarur izolyatsiyani ta'minlaydi.

AppDomain Arxitekturasi

22-1-rasm bitta Windows jarayonini ko'rsatadi, unda bitta CLR COM serveri ishlaydi va hozirda ikkita AppDomain ni boshqarayapti (garchi bitta Windows jarayonida ishlashi mumkin bo'lgan AppDomainlar soniga qat'iy cheklov yo'q bo'lsa ham). Har bir AppDomain o'zining yuklash uyumiga (loader heap) ega bo'lib, ularning har biri AppDomain yaratilganligi sababli murojaat qilingan turlarning yozuvini yuritadi.

AppDomain tuzilishi

Har bir AppDomain o'ziga yuklangan assemblylar to'plamiga ega. Masalan, AppDomain #1 (standart AppDomain) uchta assembly ga ega bo'lishi mumkin: MyApp.exe, TypeLib.dll va System.dll. AppDomain #2 esa ikkita assemblyga ega: Wintellect.dll va System.dll. System.dll assembliyasi ikkala AppDomainga yuklanganini sezasiz — agar ikkala AppDomain System.dll dan bitta turni ishlatayotgan bo'lsa, ikkala AppDomain ning yuklash uyumida bitta tur ob'ektiga ega bo'ladi; tur ob'ekti uchun xotira barcha AppDomainlar tomonidan baham ko'rilmaydi.

Ba'zi assemblylar bir nechta AppDomain lar tomonidan ishlatilishi kutiladi. MSCorLib.dll buning eng yaxshi misolidir. Ushbu assembly System.Object, System.Int32 va .NET Framework uchun shunchalik muhim bo'lgan barcha boshqa turlarni o'z ichiga oladi. Bu assembly CLR ishga tushganda avtomatik ravishda yuklanadi va barcha AppDomainlar ushbu assemblydagi turlarni baham ko'radi. Resurs sarfini kamaytirish uchun MSCorLib.dll domen-neytral (domain-neutral) tarzda yuklanadi; ya'ni CLR domen-neytral tarzda yuklangan assemblylar uchun maxsus yuklash uyumini saqlaydi. Ushbu yuklash uyumidagi barcha tur ob'ektlari va ushbu turlarning metodlari uchun barcha native kod jarayondagi barcha AppDomainlar tomonidan baham ko'riladi.

Afsuski, ushbu resurslarni baham ko'rishning narxi bor: domen-neytral yuklangan assemblylarni hech qachon tushirib bo'lmaydi. Ular tomonidan ishlatilgan resurslarni qaytarib olishning yagona yo'li Windows jarayonini tugatib, Windows ga resurslarni tozalashdir.

Obyektlarga AppDomain Chegaralari Orqali Murojaat Qilish

Bitta AppDomain dagi kod boshqa AppDomain da joylashgan turlar va obyektlar bilan aloqa o'rnatishi mumkin. Biroq, ushbu turlarga va obyektlarga kirish faqat aniq belgilangan mexanizmlar orqali ruxsat beriladi. Quyidagi Ch22-1-AppDomains namunali ilovasi yangi AppDomain yaratish, unga assembly yuklash va o'sha assembliyada aniqlangan turning instansiyasini qurish usulini ko'rsatadi. Kod reference orqali marshal qilinadigan, qiymat orqali marshal qilinadigan va umuman marshal qilib bo'lmaydigan turlarni yaratishdagi turli xatti-harakatlarni hamda ushbu turli marshalled obyektlar AppDomain tushirilganida qanday o'zini tutishini ko'rsatadi.

private static void Marshalling() {
    // Chaqiruvchi oqim ishlayotgan AppDomain ga havola olish
    AppDomain adCallingThreadDomain = Thread.GetDomain();

    // Har bir AppDomain ga do'stona satr nomi beriladi (nosozliklarni tuzatish uchun foydali)
    // Ushbu AppDomain ning do'stona nomini olib ko'rsatish
    String callingDomainName = adCallingThreadDomain.FriendlyName;
    Console.WriteLine("Default AppDomain's friendly name={0}", callingDomainName);

    // 'Main' metodini o'z ichiga olgan AppDomain dagi assemblyni olish va ko'rsatish
    String exeAssembly = Assembly.GetEntryAssembly().FullName;
    Console.WriteLine("Main assembly={0}", exeAssembly);

    // AppDomain ga murojaat qilishi mumkin bo'lgan lokal o'zgaruvchini aniqlash
    AppDomain ad2 = null;

    // *** DEMO 1: Marshal-by-Reference yordamida AppDomain o'rtasidagi aloqa ***
    Console.WriteLine("{0}Demo #1", Environment.NewLine);

    // Yangi AppDomain yaratish (xavfsizlik va konfiguratsiya joriy AppDomain bilan mos)
    ad2 = AppDomain.CreateDomain("AD #2", null, null);
    MarshalByRefType mbrt = null;

    // Assembly ni yangi AppDomain ga yuklash, obyektni yaratish, marshal qilish
    // uni bizning AD ga qaytarish (biz aslida proxy ga havola olamiz)
    mbrt = (MarshalByRefType)
       ad2.CreateInstanceAndUnwrap(exeAssembly, "MarshalByRefType");

    Console.WriteLine("Type={0}", mbrt.GetType());  // CLR tur haqida yolg'on aytadi

    // Biz proxy obyektiga havola olganimizni isbotlash
    Console.WriteLine("Is proxy={0}", RemotingServices.IsTransparentProxy(mbrt));

    // Bu MarshalByRefType da metod chaqirayotgandek ko'rinadi lekin shunday emas.
    // Biz proxy turidagi metodni chaqirayapmiz. Proxy oqimni
    // obyektga egalik qiluvchi AppDomain ga o'tkazadi va haqiqiy obyektda
    // ushbu metodni chaqiradi.
    mbrt.SomeMethod();

    // Yangi AppDomain ni tushirish
    AppDomain.Unload(ad2);
    // mbrt hali ham yaroqli proxy obyektiga ishora qiladi;
    // proxy obyekt yaroqsiz AppDomain ga ishora qiladi

    try {
        // Biz proxy turidagi metod chaqirayapmiz. AD yaroqsiz, istisno chiqariladi
        mbrt.SomeMethod();
        Console.WriteLine("Successful call.");
    }
    catch (AppDomainUnloadedException) {
        Console.WriteLine("Failed call.");
    }


    // *** DEMO 2: Marshal-by-Value yordamida AppDomain o'rtasidagi aloqa ***
    Console.WriteLine("{0}Demo #2", Environment.NewLine);

    // Yangi AppDomain yaratish (xavfsizlik va konfiguratsiya joriy AppDomain bilan mos)
    ad2 = AppDomain.CreateDomain("AD #2", null, null);
    mbrt = (MarshalByRefType)
       ad2.CreateInstanceAndUnwrap(exeAssembly, "MarshalByRefType");

    // Obyektning metodi qaytarilgan obyektning NUSXASINI qaytaradi;
    // obyekt qiymat bo'yicha marshal qilinadi (havola bo'yicha emas).
    MarshalByValType mbvt = mbrt.MethodWithReturn();

    // Biz proxy obyektiga havola OLMAGANMIZNI isbotlash
    Console.WriteLine("Is proxy={0}", RemotingServices.IsTransparentProxy(mbvt));

    // Bu MarshalByValType da metod chaqirayotgandek ko'rinadi va shunday ham.
    Console.WriteLine("Returned object created " + mbvt.ToString());

    // Yangi AppDomain ni tushirish
    AppDomain.Unload(ad2);
    // mbvt yaroqli obyektga ishora qiladi; AppDomain ni tushirish ta'sir qilmaydi.

    try {
        // Biz obyektda metod chaqirayapmiz; hech qanday istisno chiqmaydi
        Console.WriteLine("Returned object created " + mbvt.ToString());
        Console.WriteLine("Successful call.");
    }
    catch (AppDomainUnloadedException) {
        Console.WriteLine("Failed call.");
    }


    // DEMO 3: Marshal qilib bo'lmaydigan tur bilan AppDomain o'rtasidagi aloqa ***
    Console.WriteLine("{0}Demo #3", Environment.NewLine);

    // Yangi AppDomain yaratish (xavfsizlik va konfiguratsiya joriy AppDomain bilan mos)
    ad2 = AppDomain.CreateDomain("AD #2", null, null);

    // Assembly ni yangi AppDomain ga yuklash, obyektni yaratish, marshal qilish
    // uni bizning AD ga qaytarish (biz aslida proxy ga havola olamiz)
    mbrt = (MarshalByRefType)
       ad2.CreateInstanceAndUnwrap(exeAssembly, "MarshalByRefType");

    // Obyektning metodi marshal qilib bo'lmaydigan obyektni qaytaradi; istisno
    NonMarshalableType nmt = mbrt.MethodArgAndReturn(callingDomainName);
    // Biz bu yerga yetib kelmaymiz...
}

Quyida yuqoridagi kod tomonidan ishlatiladigan tur ta'riflari keltirilgan:

// Instansiyalar AppDomain chegaralari bo'ylab reference orqali marshal qilinishi mumkin
public sealed class MarshalByRefType : MarshalByRefObject {
    public MarshalByRefType() {
        Console.WriteLine("{0} ctor running in {1}",
            this.GetType().ToString(), Thread.GetDomain().FriendlyName);
    }

    public void SomeMethod() {
        Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
    }

    public MarshalByValType MethodWithReturn() {
        Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
        MarshalByValType t = new MarshalByValType();
        return t;
    }

    public NonMarshalableType MethodArgAndReturn(String callingDomainName) {
        // ESLATMA: callingDomainName [Serializable]
        Console.WriteLine("Calling from '{0}' to '{1}'.",
           callingDomainName, Thread.GetDomain().FriendlyName);
        NonMarshalableType t = new NonMarshalableType();
        return t;
    }
}

// Instansiyalar AppDomain chegaralari bo'ylab qiymat orqali marshal qilinishi mumkin
[Serializable]
public sealed class MarshalByValType : Object {
    private DateTime m_creationTime = DateTime.Now; // ESLATMA: DateTime [Serializable]

    public MarshalByValType() {
        Console.WriteLine("{0} ctor running in {1}, Created on {2:D}",
            this.GetType().ToString(),
            Thread.GetDomain().FriendlyName,
            m_creationTime);
    }

    public override String ToString() {
        return m_creationTime.ToLongDateString();
    }
}

// Instansiyalar AppDomain chegaralari bo'ylab marshal qilinishi MUMKIN EMAS
// [Serializable]
public sealed class NonMarshalableType : Object {
    public NonMarshalableType() {
        Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
    }
}

Ushbu ilovani kompilyatsiya qilib ishga tushirsangiz, quyidagi natijani olasiz:

Default AppDomain's friendly name= Ch22-1-AppDomains.exe
Main assembly=Ch22-1-AppDomains, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

Demo #1
MarshalByRefType ctor running in AD #2
Type=MarshalByRefType
Is proxy=True
Executing in AD #2
Failed call.

Demo #2
MarshalByRefType ctor running in AD #2
Executing in AD #2
MarshalByValType ctor running in AD #2, Created on Friday, August 07, 2009
Is proxy=False
Returned object created Saturday, June 23, 2012
Returned object created Saturday, June 23, 2012
Successful call.

Demo #3
MarshalByRefType ctor running in AD #2
Calling from 'Ch22-1-AppDomains.exe' to 'AD #2'.
Executing in AD #2

Unhandled Exception: System.Runtime.Serialization.SerializationException:
Type 'NonMarshalableType' in assembly 'Ch22-1-AppDomains, Version=0.0.0.0,
Culture=neutral, PublicKeyToken=null' is not marked as serializable.
at MarshalByRefType.MethodArgAndReturn(String callingDomainName)
at Program.Marshalling()
at Program.Main()

Demo #1: Marshal-by-Reference yordamida Cross-AppDomain Aloqasi

Marshalling metodi ichida men avval chaqiruvchi oqim hozirda ishlayotgan AppDomain ni aniqlaydigan AppDomain obyektiga havola olaman. Windows da oqim doimo bitta jarayondagi kontekstda yaratiladi va bu jarayon uchun butun umri davomida yashaydi. Biroq, oqimlar va AppDomainlar o'rtasida birma-bir muvofiqlik yo'q; AppDomainlar CLR xususiyatidir; Windows AppDomainlar haqida hech narsa bilmaydi.

Demo #1 da System.AppDomain ning statik CreateDomain metodi chaqirilib, CLR ga bitta Windows jarayoni ichida yangi AppDomain yaratishni buyuradi. CreateDomain metodi uchta argument qabul qiladi:

  • Yangi AppDomain ga berilishi kerak bo'lgan do'stona nomni aniqlaydigan String. Men bu yerda "AD #2" uzatayapman.
  • System.Security.Policy.Evidence — CLR AppDomain ning ruxsat to'plamini hisoblash uchun ishlatishi kerak bo'lgan dalilni aniqlaydi. Men bu yerda null uzatayapman, shuning uchun yangi AppDomain uni yaratayotgan AppDomain bilan bir xil ruxsat to'plamiga ega bo'ladi.
  • System.AppDomainSetup — CLR yangi AppDomain uchun ishlatishi kerak bo'lgan konfiguratsiya sozlamalarini aniqlaydi. Yana null uzatayapman, shuning uchun yangi AppDomain uni yaratayotgan AppDomain dan konfiguratsiya sozlamalarini meros oladi.

Yangi AppDomain da obyektning instansiyasini yaratish uchun avval yangi AppDomain ga assembly yuklash kerak va keyin o'sha assembliyada aniqlangan turning instansiyasini yaratish kerak. Aynan AppDomain ning CreateInstanceAndUnwrap metodi shunday qiladi. Ushbu metodga ikkita argument uzataman: yuklamoqchi bo'lgan assemblyni aniqlaydigan String va instansiya yaratmoqchi bo'lgan tur nomini aniqlaydigan boshqa String.

Ichki tarzda, CreateInstanceAndUnwrap chaqiruvchi oqimni joriy AppDomain dan yangi AppDomain ga o'tkazadi. Endi oqim (u CreateInstanceAndUnwrap chaqiruvining ichida) belgilangan assemblyni yangi AppDomain ga yuklaydi va assemblyning tur ta'rifi metadata jadvalini skanerlaydi, belgilangan turni ("MarshalByRefType") qidiradi. Tur topilgach, oqim MarshalByRefType ning parametrsiz konstruktorini chaqiradi. Keyin oqim CreateInstanceAndUnwrap yangi MarshalByRefType obyektiga havolani qaytarishi uchun standart AppDomain ga qaytadi.

Eslatma

CreateInstanceAndUnwrap ning haddan tashqari yuklangan versiyalari mavjud bo'lib, ular turning konstruktoriga argumentlarni uzatish orqali chaqirishga imkon beradi.

Bu hammasi yaxshi tuyulsa-da, muammo bor: CLR bitta AppDomain da yashovchi o'zgaruvchiga (root) boshqa AppDomain da yaratilgan obyektga havolani o'rnatishga ruxsat bera olmaydi. Agar CreateInstanceAndUnwrap shunchaki obyektga havolani qaytarsa, izolyatsiya buziladi va izolyatsiya AppDomainlarning butun maqsadidir! Shuning uchun, CreateInstanceAndUnwrap havolani qaytarishdan oldin u qo'shimcha mantiq bajaradi.

MarshalByRefType turi juda muhim bazaviy sinf: System.MarshalByRefObject dan kelib chiqishini sezasiz. CreateInstanceAndUnwrap turi MarshalByRefObject dan kelib chiqqan obyektni marshalling qilayotganini ko'rganda, CLR obyektni reference orqali AppDomain chegaralari bo'ylab marshal qiladi.

Manba AppDomain obyektga havolani maqsad AppDomain ga yubormoqchi yoki qaytarmoqchi bo'lganda, CLR maqsad AppDomain ning yuklash uyumida proxy turini aniqlaydi. Bu proxy turi asl turning metama'lumotlari yordamida aniqlanadi va shuning uchun asl turga aynan o'xshab ko'rinadi; u barcha bir xil instansiya a'zolariga (xususiyatlar, hodisalar va metodlar) ega. Instansiya maydonlari tur qismidan emas. Ushbu yangi turning ba'zi instansiya maydonlari bor, lekin bu maydonlar asl ma'lumot turining maydonlariga o'xshamaydi. Buning o'rniga bu maydonlar qaysi AppDomain haqiqiy obyektga "egalik qilishini" va egalik qiluvchi AppDomain dagi haqiqiy obyektni qanday topishni ko'rsatadi. (Ichki tarzda proxy obyekti haqiqiy obyektga ishora qiluvchi GCHandle instansiyasidan foydalanadi.)

Proxy dagi SomeMethod metodi chaqirilganda, proxy ni amalga oshirish ichki maydonlardagi ma'lumotdan foydalanib chaqiruvchi oqimni standart AppDomain dan yangi AppDomain ga o'tkazadi. Endi oqim yangi AppDomain ning xavfsizlik va konfiguratsiya sozlamalari ostida ishlaydi. Keyin oqim proxy obyektining GCHandle maydonidan foydalanib yangi AppDomain dagi haqiqiy obyektni topadi va haqiqiy SomeMethod metodini chaqirish uchun foydalanadi.

Eslatma

Bitta AppDomain dagi oqim boshqa AppDomain dagi metodni chaqirganda, oqim ikkala AppDomain o'rtasida o'tadi. Bu AppDomain chegaralari bo'ylab metod chaqiruvlari sinxron bajarilishini anglatadi. Biroq, istalgan vaqtda oqim bitta AppDomain da deb hisoblanadi va ushbu AppDomain ning xavfsizlik va konfiguratsiya sozlamalari bilan kod bajaradi. Agar siz bir vaqtning o'zida bir nechta AppDomain larda kod bajarishni xohlasangiz, qo'shimcha oqimlar yaratib, ularni xohlagan AppDomain larida xohlagan kodni bajarishga yo'naltirishingiz kerak.

Keyin Ch22-1-AppDomains ilovasi AppDomain.Unload metodini chaqirib CLR ni belgilangan AppDomain ni tushirishga, unga yuklangan barcha assemblylarni va undagi kod tomonidan yaratilgan barcha obyektlarni bo'shatishga majbur qiladi. Standart AppDomain ning mbrt o'zgaruvchisi hali ham yaroqli proxy obyektiga ishora qiladi; biroq proxy obyekti endi yaroqli AppDomain ga ishora qilmaydi (chunki u tushirilgan).

Standart AppDomain proxy orqali SomeMethod metodini chaqirishga uringanda, proxy ni amalga oshirish haqiqiy obyektni o'z ichiga olgan AppDomain tushirilganligini aniqlaydi va proxy ning SomeMethod metodi chaqiruvchiga operatsiya yakunlanmaganligini bildirish uchun AppDomainUnloadedException chiqaradi.

Ishlash bo'yicha diqqat

MarshalByRefObject dan kelib chiqqan tur instansiya maydonlarini aniqlashi mumkin. Biroq bu instansiya maydonlari proxy turi ichida emas va proxy obyektida joylashmagan. MarshalByRefObject dan kelib chiqqan turning maydoni o'qilganda yoki yozilganda, JIT kompilyatori haqiqiy AppDomain/obyektni topish uchun proxy obyektdan foydalanadigan System.Object ning FieldGetter yoki FieldSetter metodlarini chaqiradigan kod chiqaradi. Bu metodlar reflectiondan foydalanadi, shu sababli ishlash nihoyatda yomon. Aslida, ishlash obyektga o'z AppDomain ingizda murojaat qilayotgan bo'lsangiz ham yomon.

Foydalanish nuqtai nazaridan, MarshalByRefObject dan kelib chiqqan tur hech qanday statik a'zolar aniqlamasligi kerak. Buning sababi shundaki, statik a'zolar doimo chaqiruvchi AppDomain kontekstida murojaat qilinadi. Hech qanday AppDomain o'tishi sodir bo'lmaydi, chunki proxy obyekti qaysi AppDomain ga o'tish haqidagi ma'lumotni o'z ichiga oladi, lekin statik a'zoni chaqirayotganda hech qanday proxy obyekti yo'q.

Ikkinchi AppDomain da proxy tomonidan ishorat qilingan asl obyektga hech qanday rootlar bo'lmaganligi sababli, asl obyekt axlat yig'ish orqali yo'q qilinishi mumkin. CLR bu muammoni ijara menejeri (lease manager) yordamida hal qiladi. Proxy obyektga yaratilganda CLR obyektni besh daqiqa tirik saqlaydi. Agar besh daqiqa ichida proxy orqali hech qanday chaqiruv amalga oshirilmasa, obyekt faolsizlantiriladi va uning xotirasi keyingi axlat yig'ishda bo'shatiladi. Har bir chaqiruvdan keyin ijara menejeri obyekt ijarasini faolsizlantirilishidan oldin yana ikki daqiqa xotirada qolishi kafolatlanishi uchun yangilaydi. Muddati o'tgan obyektga proxy orqali murojaat qilishga uringan ilova System.Runtime.Remoting.RemotingException oladi.

Demo #2: Marshal-by-Value yordamida Cross-AppDomain Aloqasi

Demo #2 Demo #1 ga juda o'xshash. Yana bir AppDomain yaratiladi va CreateInstanceAndUnwrap chaqirilib yangi AppDomain ga bir xil assemblyni yuklab, unda MarshalByRefType obyektini yaratadi. Keyin CLR proxy yaratadi va standart AppDomain dagi mbrt o'zgaruvchisi proxy ga ishora qilib initsializatsiya qilinadi.

Endi proxy orqali men MethodWithReturn metodini chaqiraman. Bu metod argumentsiz bo'lib, yangi AppDomain da MarshalByValType turining instansiyasini yaratadi va standart AppDomain ga obyektga havolani qaytaradi.

MarshalByValType System.MarshalByRefObject dan kelib chiqmagan va shuning uchun CLR proxy tur aniqlab instansiya yaratish uchun foydalana olmaydi; obyekt AppDomain chegarasi bo'ylab reference orqali marshal qilinishi mumkin emas.

Biroq, MarshalByValType [Serializable] maxsus atributi bilan belgilanganligi sababli, MethodWithReturn obyektni qiymat orqali marshal qilishi mumkin. Manba AppDomain obyektga havolani maqsad AppDomain ga yubormoqchi yoki qaytarmoqchi bo'lganda, CLR obyektning instansiya maydonlarini bayt massiviga seriallaydi. Bu bayt massivi manba AppDomain dan maqsad AppDomain ga ko'chiriladi. Keyin CLR maqsad AppDomain da bayt massividagi qiymatlardan foydalanib obyektning maydonlarini initsializatsiya qiladi. Boshqacha aytganda, CLR maqsad AppDomain dagi manba obyektning aniq dublikatini yaratadi va nusxaga havolani qaytaradi; obyekt AppDomain chegarasi bo'ylab qiymat orqali marshal qilindi.

Muhim

Assembly yuklashda CLR maqsad AppDomain ning siyosatlarini va konfiguratsiya sozlamalarini ishlatadi (masalan, AppDomain boshqa AppBase katalogiga yoki turli versiya bog'lash yo'naltirmalariga ega bo'lishi mumkin). Bu siyosat farqlari CLR ni assemblyni topishdan to'sqinlik qilishi mumkin. Agar assembly yuklanmasa, istisno chiqariladi va maqsad obyektga havola olmaydi.

Bu nuqtada manba AppDomain dagi obyekt va maqsad AppDomain dagi obyekt mustaqil hayot kechiradi va ularning holatlari bir-biridan mustaqil ravishda o'zgarishi mumkin. Agar manba AppDomain da asl obyektni tirik saqlaydigan hech qanday root bo'lmasa, uning xotirasi keyingi axlat yig'ishda bo'shatiladi.

mbvt o'zgaruvchisi haqiqiy obyektga ishora qilganligi sababli (IsTransparentProxy false qaytaradi), ToString metodi to'g'ridan-to'g'ri chaqiriladi va hech qanday AppDomain o'tishi sodir bo'lmaydi. AppDomain tushirilgandan keyin ham, qiymat orqali marshalled bo'lgan obyektlar standart AppDomain da muammosiz ishlashda davom etadi.

Demo #3: Marshal Qilib Bo'lmaydigan Turlar Bilan Cross-AppDomain Aloqasi

Demo #3 Demo #1 va #2 ga o'xshash boshlanadi. AppDomain yaratiladi, assembly yuklanadi va MarshalByRefType obyekti yaratiladi. Keyin proxy orqali argument qabul qiladigan MethodArgAndReturn metodi chaqiriladi.

CLR argumentni yangi AppDomain ga uzatishda izolyatsiyani saqlashi kerak. Agar obyekt turi MarshalByRefObject dan kelib chiqqan bo'lsa, CLR unga proxy yaratadi va reference orqali marshal qiladi. Agar obyekt turi [Serializable] deb belgilangan bo'lsa, CLR uni bayt massiviga seriallab qiymat orqali marshal qiladi.

Bu misolda men System.String obyektini AppDomain chegarasi bo'ylab uzatyapman. System.String turi MarshalByRefObject dan kelib chiqmagan, shuning uchun CLR proxy yarata olmaydi. Yaxshiyamki, System.String [Serializable] deb belgilangan va shuning uchun CLR uni qiymat orqali marshal qilishi mumkin. String obyektlari uchun CLR maxsus optimizatsiya qiladi: String obyektini AppDomain chegarasi bo'ylab marshal qilayotganda CLR shunchaki String obyektiga havolani chegaradan o'tkazadi; String obyektining nusxasini yaratmaydi. CLR buni String obyektlari o'zgarmas (immutable) bo'lgani uchun taklif qilishi mumkin.

MethodArgAndReturn ichida NonMarshalableType ning instansiyasi yaratiladi va qaytariladi. NonMarshalableType System.MarshalByRefObject dan kelib chiqmagan va [Serializable] atributi bilan belgilanmagan, shuning uchun MethodArgAndReturn obyektni na reference, na qiymat orqali marshal qila oladi — uni AppDomain chegarasi bo'ylab umuman marshal qilib bo'lmaydi! Bu haqda xabar berish uchun MethodArgAndReturn standart AppDomain da SerializationException chiqaradi. Dasturim bu istisnoni ushlanganligi sababli dastur shunchaki to'xtaydi.

AppDomain ni Tushirish (Unloading)

AppDomainlarning ajoyib xususiyatlaridan biri ularni tushirish mumkinligidir. AppDomain ni tushirish CLR ga AppDomain dagi barcha assemblylarni tushirishga va AppDomain ning yuklash uyumini bo'shatishga olib keladi. AppDomain ni tushirish uchun AppDomain ning Unload statik metodini chaqirish kerak. Bu CLR ga belgilangan AppDomain ni engil tarzda tushirish uchun bir qator amallarni bajarishga olib keladi:

  1. CLR jarayonda boshqariluvchi kodni bajargan barcha oqimlarni to'xtatadi.
  2. CLR barcha oqimlarning steklarini tekshirib, qaysi oqimlar tushirilayotgan AppDomain da hozir kod bajarayotganini yoki qaysi oqimlar kelajakda ushbu AppDomain dagi kodga qaytishi mumkinligini aniqlaydi. CLR tushirilayotgan AppDomain da joylashgan oqimlarni ThreadAbortException chiqarishga majbur qiladi (oqim bajarilishini davom ettirib), bu oqimlarni finally bloklarini bajarib, tozalash kodini bajarishga olib keladi. Hech qanday kod ThreadAbortException ni ushlamasa, u qayta ishlanmagan istisno bo'lib, CLR uni yutib yuboradi; oqim o'ladi, lekin jarayon ishlashda davom etadi. Bu g'ayrioddiy, chunki boshqa barcha qayta ishlanmagan istisnolar uchun CLR jarayonni tugatadi.
  3. 2-bosqichda aniqlangan barcha oqimlar AppDomain ni tark etgandan keyin, CLR uyumni ko'rib chiqadi va tushirilgan AppDomain tomonidan yaratilgan obyektga ishora qilgan har bir proxy obyektidagi bayroqni o'rnatadi. Bu proxy obyektlar endi ular ishora qilgan haqiqiy obyekt yo'q bo'lganini biladi.
  4. CLR axlat yig'ishni amalga oshirib, endi tushirilgan AppDomain tomonidan yaratilgan ixtiyoriy obyektlar tomonidan ishlatilgan xotirani qayta oladi. Bu obyektlar uchun Finalize metodlari chaqiriladi.
  5. CLR qolgan barcha oqimlarni davom ettiradi. AppDomain.Unload ni chaqirgan oqim endi ishlashda davom etadi; AppDomain.Unload ga chaqiruvlar sinxron bo'ladi.

Aytgancha, oqim AppDomain.Unload ni chaqirganda, CLR tushirilayotgan AppDomain dagi oqimlarga 10 soniya kutadi. Agar 10 soniyadan keyin AppDomain.Unload ni chaqirgan oqim qaytmasa, u CannotUnloadAppDomainException chiqaradi va AppDomain kelajakda tushirilishi yoki tushirilmasligi mumkin.

Muhim

CLR hozirda finally blokida, catch blokida, class konstruktorida, muhim bajarish mintaqasida (critical execution region) yoki boshqarilmagan kodda ishlaydigan oqimni darhol to'xtatmaydi. Agar CLR buni ruxsat bersa, tozalash kodi, xatolarni tiklash kodi, muhim kod yoki ixtiyoriy kod to'liq bajarilmasligi mumkin, natijada ilova oldindan aytib bo'lmaydigan tarzda o'zini tutadi va xavfsizlik teshiklari paydo bo'lishi mumkin. To'xtatilayotgan oqimga ushbu kod bloklarini bajarib tugatishga ruxsat beriladi va keyin, kod blokining oxirida CLR oqimga ThreadAbortException chiqarishga majbur qiladi.

Eslatma

Agar AppDomain.Unload ni chaqirgan oqim tushirilayotgan AppDomain da bo'lsa, CLR birinchi oqimga ThreadAbortException ni majburan chiqarib, AppDomain ni tushirishga harakat qiluvchi boshqa oqim yaratadi. Yangi oqim AppDomain tushirilishini kutadi va keyin tugatiladi. Agar AppDomain tushirilmasa, yangi oqim CannotUnloadAppDomainException ni qayta ishlaydi.

AppDomain Monitoring

Host ilovasi AppDomain iste'mol qiladigan resurslarni kuzatishi mumkin. Ba'zi hostlar bu ma'lumotni AppDomain ning xotirasi yoki CPU iste'moli host oqilona deb hisoblaydigan darajadan oshganda AppDomain ni majburan tushirishga qaror qilish uchun ishlatadi. Monitoring shuningdek turli algoritmlarning resurs sarfini solishtirish uchun ishlatilishi mumkin. AppDomain monitoringi qo'shimcha yuk keltirishi sababli, hostlar AppDomain ning statik MonitoringIsEnabled xususiyatini true ga o'rnatish orqali monitoringni aniq yoqishi kerak. Bu barcha AppDomainlar uchun monitoringni yoqadi. Monitoring yoqilgandan keyin uni o'chirib bo'lmaydi; MonitoringIsEnabled xususiyatini false ga o'rnatishga urinish ArgumentException chiqaradi.

Monitoring yoqilganidan keyin, kodingiz AppDomain sinfi tomonidan taklif etiladigan quyidagi to'rtta faqat-o'qish xususiyatini so'rashi mumkin:

  • MonitoringSurvivedProcessMemorySize — ushbu statik Int64 xususiyati joriy CLR instansiyasi tomonidan boshqariladigan barcha AppDomainlar tomonidan hozirda foydalanilayotgan baytlar sonini qaytaradi. Raqam oxirgi axlat yig'ish holatiga aniq.
  • MonitoringTotalAllocatedMemorySize — ushbu instansiya Int64 xususiyati ma'lum AppDomain tomonidan ajratilgan baytlar sonini qaytaradi. Raqam oxirgi axlat yig'ish holatiga aniq.
  • MonitoringSurvivedMemorySize — ushbu instansiya Int64 xususiyati ma'lum AppDomain tomonidan hozirda foydalanilayotgan baytlar sonini qaytaradi. Raqam oxirgi axlat yig'ish holatiga aniq.
  • MonitoringTotalProcessorTime — ushbu instansiya TimeSpan xususiyati ma'lum AppDomain tomonidan sarflangan CPU vaqtini qaytaradi.

Quyidagi sinf ikki nuqta orasida AppDomain ichida nima o'zgarganligini ko'rish uchun ushbu xususiyatlarning uchtasidan qanday foydalanishni ko'rsatadi:

private sealed class AppDomainMonitorDelta : IDisposable {
    private AppDomain m_appDomain;
    private TimeSpan m_thisADCpu;
    private Int64 m_thisADMemoryInUse;
    private Int64 m_thisADMemoryAllocated;

    static AppDomainMonitorDelta() {
        // AppDomain monitoringi yoqilganligiga ishonch hosil qilish
        AppDomain.MonitoringIsEnabled = true;
    }

    public AppDomainMonitorDelta(AppDomain ad) {
        m_appDomain = ad ?? AppDomain.CurrentDomain;
        m_thisADCpu = m_appDomain.MonitoringTotalProcessorTime;
        m_thisADMemoryInUse = m_appDomain.MonitoringSurvivedMemorySize;
        m_thisADMemoryAllocated = m_appDomain.MonitoringTotalAllocatedMemorySize;
    }

    public void Dispose() {
        GC.Collect();
        Console.WriteLine("FriendlyName={0}, CPU={1}ms", m_appDomain.FriendlyName,
            (m_appDomain.MonitoringTotalProcessorTime - m_thisADCpu).TotalMilliseconds);
        Console.WriteLine("   Allocated {0:N0} bytes of which {1:N0} survived GCs",
            m_appDomain.MonitoringTotalAllocatedMemorySize - m_thisADMemoryAllocated,
            m_appDomain.MonitoringSurvivedMemorySize - m_thisADMemoryInUse);
    }
}

Quyidagi kod AppDomainMonitorDelta sinfidan qanday foydalanishni ko'rsatadi:

private static void AppDomainResourceMonitoring() {
    using (new AppDomainMonitorDelta(null)) {
        // Taxminan 10 million bayt ajratish, axlat yig'ishdan omon qoladi
        var list = new List<Object>();
        for (Int32 x = 0; x < 1000; x++) list.Add(new Byte[10000]);

        // Taxminan 20 million bayt ajratish, axlat yig'ishdan omon QOLMAYDI
        for (Int32 x = 0; x < 2000; x++) new Byte[10000].GetType();

        // CPU ni taxminan 5 soniya aylantirib turing
        Int64 stop = Environment.TickCount + 5000;
        while (Environment.TickCount < stop) ;
    }
}

Ushbu kodni bajarganingizda quyidagi natijani olasiz:

FriendlyName=03-Ch22-1-AppDomains.exe, CPU=5031.25ms
   Allocated 30,159,496 bytes of which 10,085,080 survived GCs

AppDomain First-Chance Exception Notifications

Har bir AppDomain bilan istisno chiqarilganda chaqiriladigan callback metodlarining ketma-ketligi bog'langan. Bu metodlar logni yuritish yoki host AppDomain ichida chiqarilayotgan istisnolarni kuzatish uchun ishlatilishi mumkin. Callbacklar istisnoni qayta ishlash yoki yutish imkoniga ega emas; ular faqat istisno yuz berganligi haqida bildirishnoma olmoqda. Callback metodini ro'yxatga olish uchun AppDomain ning instansiya FirstChanceException hodisasiga delegat qo'shing.

CLR istisnoni quyidagicha qayta ishlaydi: istisno birinchi marta chiqarilganda, CLR istisnoni chiqarayotgan AppDomain bilan ro'yxatga olingan har qanday FirstChanceException callback metodlarini chaqiradi. Keyin CLR stek bo'ylab bir xil AppDomain ichidagi catch bloklarini qidiradi. Agar catch bloki istisnoni qayta ishlasa, istisnoni qayta ishlash yakunlangan va bajarish normal davom etadi. Agar AppDomain da istisnoni qayta ishlaydigan catch bloki bo'lmasa, CLR stekni chaqiruvchi AppDomain ga qaytaradi va bir xil istisno obyektini qayta chiqaradi (seriallash va deserializatsiyadan keyin). Bu nuqtada, xuddi yangi istisno chiqarilayotgandek, CLR hozirgi AppDomain bilan ro'yxatga olingan FirstChanceException callback metodlarini chaqiradi. Bu oqimning stekining tepasiga yetguncha davom etadi. Agar istisno hech qanday kod tomonidan qayta ishlanmasa, CLR butun jarayonni tugatadi.

Hostlar AppDomainlarni Qanday Ishlatadi

Hozirgacha men hostlarning CLR ni qanday yuklanishi va hostlarning CLR ga AppDomainlarni yaratish va tushirishni qanday buyurishi haqida gapirdim. Muhokamani yanada aniqroq qilish uchun men ba'zi umumiy hosting va AppDomain senariylarini tasvirlayman. Xususan, turli ilova turlari CLR ni qanday host qilishini va AppDomainlarni qanday boshqarishini tushuntiraman.

Bajariladigan Ilovalar

Konsol UI ilovalari, NT Service ilovalari, Windows Forms ilovalari va Windows Presentation Foundation (WPF) ilovalari boshqariluvchi EXE fayllariga ega bo'lgan o'z-o'zini host qiladigan ilovalarning barcha misollaridir. Windows boshqariluvchi EXE faylidan foydalanib jarayonni ishga tushirganda, Windows shimni yuklaydi va shim ilovaning assemblyasida (EXE faylida) joylashgan CLR header ma'lumotlarini tekshiradi. Header ma'lumotlari ilovani qurish va sinovdan o'tkazish uchun ishlatilgan CLR versiyasini ko'rsatadi. Shim ushbu ma'lumotdan CLR ning qaysi versiyasini jarayonga yuklash kerakligini aniqlash uchun foydalanadi. CLR yuklangandan va ishga tushgandan keyin, u yana assemblyning CLR headerini tekshirib ilovaning kirish nuqtasi (Main) qaysi metod ekanligini aniqlaydi. CLR ushbu metodni chaqiradi va ilova ishga tushadi.

Kod ishlayotganda u boshqa turlarga murojaat qiladi. Boshqa assemblydagi turga murojaat qilganda CLR kerakli assemblyni topadi va uni bir xil AppDomain ga yuklaydi. Qo'shimcha murojaat qilingan assemblylar ham bir xil AppDomain ga yuklanadi. Ilovaning Main metodi qaytganda Windows jarayoni tugatiladi (standart AppDomain va barcha boshqa AppDomainlarni yo'q qiladi).

Eslatma

Aytgancha, agar siz Windows jarayonini uning barcha AppDomainlarini ham o'z ichiga olgan holda yopmoqchi bo'lsangiz, System.Environment ning statik Exit metodini chaqirishingiz mumkin. Exit jarayonni to'xtatishning eng engil yo'lidir, chunki u avval boshqariluvchi uyumdagi barcha obyektlarning Finalize metodlarini chaqiradi va keyin CLR tomonidan saqlanayotgan barcha boshqarilmagan COM obyektlarini bo'shatadi. Nihoyat, Exit Win32 ExitProcess funksiyasini chaqiradi.

Microsoft Silverlight Rich Internet Ilovalari

Microsoft Silverlight runtime texnologiyasi .NET Framework ning normal ish stoli versiyasidan farqli bo'lgan maxsus CLR dan foydalanadi. Silverlight runtime o'rnatilgandan keyin, Silverlight dan foydalanadigan veb-saytga o'tish brauzeringizga Silverlight CLR (CoreClr.dll) ni yuklashga olib keladi (brauzer Windows Internet Explorer bo'lmasligi mumkin — siz hatto Windows mashinasidan foydalanmayotgan bo'lishingiz ham mumkin). Sahifadagi har bir Silverlight kontroli o'zining AppDomain ida ishlaydi. Foydalanuvchi tabni yopganda yoki boshqa veb-saytga o'tganda, endi foydalanilmayotgan Silverlight kontrollari o'z AppDomainlarini tushiradi. Silverlight kodi AppDomain da cheklangan xavfsizlik sandboxi ichida ishlaydi, shuning uchun u foydalanuvchiga yoki mashinaga hech qanday zarar yetkaza olmaydi.

Microsoft ASP.NET va XML Web Services Ilovalari

ASP.NET ISAPI DLL sifatida amalga oshirilgan (ASPNet_ISAPI.dll da). Birinchi marta mijoz URL so'raganda va ISAPI DLL tomonidan qayta ishlanganda, ASP.NET CLR ni yuklaydi. Mijoz veb-ilovaga so'rov yuborganda, ASP.NET bu so'rov birinchi marta ekanligini aniqlaydi. Agar shunday bo'lsa, ASP.NET CLR ga ushbu veb-ilova uchun yangi AppDomain yaratishni buyuradi; har bir veb-ilova o'zining virtual ildiz katalogi bilan aniqlanadi. Keyin ASP.NET CLR ga veb-ilova tomonidan talab qilinadigan assemblyni ushbu yangi AppDomain ga yuklashni buyuradi, ushbu turning instansiyasini yaratadi va mijozning veb-so'rovini qondirish uchun metod chaqirishni boshlaydi.

Kelajakda mijozlar allaqachon ishlaydigan veb-ilovaga so'rov yuborganda, ASP.NET yangi AppDomain yaratmaydi; buning o'rniga mavjud AppDomain dan foydalanadi, veb-ilova turining yangi instansiyasini yaratadi va metod chaqirishni boshlaydi. Metodlar allaqachon native kodga JIT-kompilyatsiya qilingan bo'ladi, shuning uchun keyingi barcha mijoz so'rovlarini qayta ishlash samaradorligi ajoyib.

ASP.NET ning ajoyib xususiyati shundaki, veb-sayt kodi veb-serverni to'xtatmasdan tezda o'zgartirilishi mumkin. Veb-sayt faylida diskda o'zgarish aniqlanganda, ASP.NET fayllarning eski versiyasini o'z ichiga olgan AppDomain ni aniqlaydi (oxirgi ishlaydigan so'rov tugaganda) va yangi fayllar versiyalarini o'ziga yuklab yangi AppDomain yaratadi. Buning uchun ASP.NET shadow copying (soya nusxalash) deb nomlangan AppDomain xususiyatidan foydalanadi.

Microsoft SQL Server

Microsoft SQL Server boshqarilmagan ilova bo'lib, uning ko'p qismi C++ da yozilgan. SQL Server dasturchilarga boshqariluvchi kod yordamida saqlangan protseduralar yaratishga imkon beradi. Birinchi marta ma'lumotlar bazasiga boshqariluvchi kodda yozilgan saqlangan protsedurani ishga tushirish so'rovi kelganda, SQL Server CLR ni yuklaydi. Saqlangan protseduralar o'zlarining xavfsiz AppDomain larida ishlaydi, bu saqlangan protseduralarni ma'lumotlar bazasi serveriga salbiy ta'sir ko'rsatishdan saqlaydi.

Bu funksionallik mutlaqo ajoyib! Bu dasturchilar o'zlari tanlagan dasturlash tilida saqlangan protseduralar yoza olishlarini anglatadi. Saqlangan protsedura o'z kodida kuchli tiplanadigan ma'lumot obyektlaridan foydalanishi mumkin. Kod interpretatsiya qilinish o'rniga bajarilganda native kodga JIT-kompilyatsiya qilinadi. Dasturchilar Framework Class Library (FCL) yoki boshqa assemblylarda aniqlangan har qanday turlardan foydalanishlari mumkin.

O'z Tasavvuringiz

Matn muharrirlari va elektron jadvallar kabi unumdorlik ilovalari ham foydalanuvchilarga o'zlari tanlagan dasturlash tilida makroslar yozishga ruxsat berishi mumkin. Bu makroslar barcha assemblylar va CLR bilan ishlaydigan turlarga kirish imkoniyatiga ega bo'ladi. Ular kompilyatsiya qilinadi, shuning uchun tez ishlaydi va eng muhimi, bu makroslar xavfsiz AppDomain da ishlaydi, shuning uchun foydalanuvchilar hech qanday kutilmagan hodisalarga duch kelmaydi. O'z ilovalaringiz ham bu imkoniyatdan xohlagan tarzda foydalanishi mumkin.

Kengaytirilgan Host Boshqaruvi

Ushbu bo'limda men CLR ni hosting qilish bilan bog'liq ba'zi ilg'or mavzularni eslataman. Mening maqsadim sizga nima mumkinligi haqida tushuncha berish va CLR nimaga qodir ekanligini yanada yaxshiroq tushunishga yordam berishdir.

CLR ni Boshqariluvchi Kod Orqali Boshqarish

System.AppDomainManager sinfi hostga boshqarilmagan kod o'rniga boshqariluvchi koddan foydalanib CLR standart xatti-harakatini bekor qilishga imkon beradi. Boshqariluvchi kod yordamida hostni amalga oshirish osonroq. Sizga kerak bo'lgan yagona narsa sinfingizni aniqlash va uni System.AppDomainManager sinfidan kelib chiqarish, boshqaruvni o'z qo'lingizga olmoqchi bo'lgan virtual metodlarni bekor qilish (override). Sinfingiz global assembly keshga (GAC) o'rnatiladigan kuchli nomlangan assemblyga qurilishi kerak.

Keyin CLR ga AppDomainManager dan kelib chiqqan sinfingizdan foydalanishni aytishingiz kerak. Kodda buning eng yaxshi usuli AppDomainSetup obyektini yaratib, uning AppDomainManagerAssembly va AppDomainManagerType xususiyatlarini initsializatsiya qilishdir.

AppDomainManager dan kelib chiqqan sinf nima qilishi mumkinligi haqida gaplaylik. Ushbu sinfning maqsadi qo'shimcha ilova (add-in) o'z AppDomain larini yaratishga harakat qilganda ham hostga boshqaruvni saqlash imkoniyatini berishdir. Jarayondagi kod yangi AppDomain yaratishga uringanda, AppDomainManager dan kelib chiqqan obyekt ushbu AppDomain dagi xavfsizlik va konfiguratsiya sozlamalarini o'zgartirishi mumkin. U shuningdek AppDomain yaratilishini rad etishi yoki mavjud AppDomain ga havolani qaytarishga qaror qilishi mumkin.

Mustahkam Host Ilovasi Yozish

Host CLR ga boshqariluvchi kodda xatolik yuz berganda qanday choralar ko'rish kerakligini aytishi mumkin. Mana ba'zi misollar (eng engil dan eng og'ir gacha):

  • CLR oqimni to'xtatishi mumkin, agar oqim javob qaytarish uchun juda uzoq vaqt olayotgan bo'lsa. (Men buni keyingi bo'limda batafsil muhokama qilaman.)
  • CLR AppDomain ni tushirishi mumkin. Bu AppDomain dagi barcha oqimlarni to'xtatadi va muammoli kodning tushirilishiga olib keladi.
  • CLR o'chirilishi mumkin. Bu jarayon ichida boshqariluvchi kodning bajarilishini to'xtatadi, lekin boshqarilmagan kod hali ham ishlashga ruxsat beriladi.
  • CLR Windows jarayonidan chiqishi mumkin. Bu barcha oqimlarni to'xtatadi va avval barcha AppDomainlarni tushiradi, shuning uchun tozalash operatsiyalari amalga oshiriladi va keyin jarayon tugatiladi.

CLR oqimni yoki AppDomain ni engil (graceful) yoki qo'pol (rude) tarzda to'xtatishi mumkin. Engil to'xtatish tozalash kodining bajarilishini anglatadi. Boshqacha aytganda, finally bloklaridagi kod ishlaydi va obyektlarning Finalize metodlari bajariladi. Qo'pol to'xtatish tozalash kodining bajarilmasligini anglatadi. Boshqacha aytganda, finally bloklaridagi kod ishlamasligi va obyektlarning Finalize metodlari bajarilmasligi mumkin.

Engil to'xtatish catch yoki finally blokida bo'lgan oqimni to'xtata olmaydi. Biroq, qo'pol to'xtatish catch yoki finally blokida bo'lgan oqimni to'xtatishi mumkin. Afsuski, boshqarilmagan kodda yoki muhim bajarish mintaqasida (CER) bo'lgan oqimni umuman to'xtatib bo'lmaydi.

Host eskalasiia siyosatini o'rnatishi mumkin, bu CLR ga boshqariluvchi kod muvaffaqiyatsizliklari bilan qanday munosabatda bo'lishni aytadi. Masalan, SQL Server CLR ga qayta ishlanmagan istisno chiqarilganda nima qilishni aytadi. Oqim qayta ishlanmagan istisnoni boshdan kechirganda, CLR avval istisnoni engil oqim to'xtatishiga kengaytirishga harakat qiladi. Agar oqim belgilangan vaqt oralig'ida to'xtamasa, CLR engil oqim to'xtatishini qo'pol oqim to'xtatishiga kengaytirishga harakat qiladi.

Agar oqim muhim mintaqada (critical region) bo'lsa, siyosat boshqacha. Muhim mintaqa — bu bir nechta oqimlar tomonidan baham ko'riladigan sinxronlash qulfini olgan oqim bo'lgan oqim. Masalan, Monitor.Enter, Mutex ning WaitOne yoki ReaderWriterLock ning AcquireReaderLock yoki AcquireWriterLock metodlarini chaqirgan oqim. Muhim mintaqadagi oqim qayta ishlanmagan istisnoni boshdan kechirganda, CLR avval istisnoni engil AppDomain tushirishiga kengaytirishga harakat qiladi. Agar AppDomain belgilangan vaqtda tushirilmasa, CLR engil AppDomain tushirishini qo'pol AppDomain tushirishiga kengaytiradi.

Host O'z Oqimini Qanday Qaytaradi

Odatda host ilovasi o'z oqimlarini boshqaruv ostida ushlab turishni xohlaydi. Keling, ma'lumotlar bazasi serverini misol sifatida olaylik. Ma'lumotlar bazasi serveriga so'rov kelganda, oqim so'rovni qabul qiladi va keyin haqiqiy ishni bajarish uchun uni boshqa oqimga yo'naltiradi. Bu boshqa oqim ma'lumotlar bazasi serverini ishlab chiqargan jamoa tomonidan yaratilmagan va sinovdan o'tkazilmagan kodni bajarishga to'g'ri kelishi mumkin.

Masalan, ma'lumotlar bazasi serveriga serverni boshqarayotgan kompaniya tomonidan boshqariluvchi kodda yozilgan saqlangan protsedurani bajarish so'rovi kelishini tasavvur qiling. Ma'lumotlar bazasi serveri saqlangan protsedurani o'zining xavfsiz AppDomain ida ishga tushirishi va saqlangan protseduraning o'z AppDomain idan tashqaridagi obyektlarga kirishini hamda ma'lumotlar bazasi serveriga salbiy ta'sir ko'rsatishini oldini olishi ajoyib.

Lekin agar saqlangan protsedura cheksiz halqaga kirsa nima bo'ladi? Bunday holda ma'lumotlar bazasi serveri o'z oqimlaridan birini saqlangan protsedura kodiga jo'natgan va bu oqim hech qachon qaytmaydi. Bu serverni noaniq holatga qo'yadi.

Bu muammolarni hal qilish uchun host oqimni to'xtatish (thread aborting) dan foydalanishi mumkin. 22-3-rasmda boshqarib bo'lmaydigan oqim muammosini hal qilishga harakat qilayotgan host ilovasining odatiy arxitekturasi ko'rsatilgan. Mana u qanday ishlaydi:

  1. Mijoz serverga so'rov yuboradi.
  2. Server oqimi so'rovni qabul qilib, haqiqiy ishni bajarish uchun oqimlar puli oqimiga yo'naltiradi.
  3. Oqimlar puli oqimi mijoz so'rovini oladi va host ilovasini qurgan va sinovdan o'tkazgan kompaniya tomonidan yozilgan ishonchli kodni bajaradi.
  4. Bu ishonchli kod try blokiga kiradi va try bloki ichidan AppDomain chegarasi orqali (MarshalByRefObject dan kelib chiqqan tur orqali) chaqiriq qiladi. Bu AppDomain host ilovani ishlab chiqargan kompaniya tomonidan qurilmagan va sinovdan o'tkazilmagan ishonchsiz kodni (masalan, saqlangan protsedura) o'z ichiga oladi.
  5. Host dastlab mijoz so'rovini qabul qilganida vaqtni qayd etgan. Agar ishonchsiz kod mijozga administrator tomonidan belgilangan vaqt ichida javob bermasa, host Thread ning Abort metodini chaqirib, CLR dan oqimlar puli oqimini to'xtatishni so'raydi va uni ThreadAbortException chiqarishga majbur qiladi.
  6. Bu nuqtada oqimlar puli oqimi ochishni boshlaydi, tozalash kodini bajarib finally bloklarini chaqiradi. Oxir-oqibat oqimlar puli oqimi AppDomain chegarasidan qaytib o'tadi. Hostning stub kodi ishonchsiz kodni try bloki ichidan chaqirganligi sababli, hostning stub kodida ThreadAbortException ni ushlaydigan catch bloki bor.
  7. ThreadAbortException ni ushlaganda, host Thread ning ResetAbort metodini chaqiradi. Bu chaqiruvning maqsadini tez orada tushuntiraman.
  8. Endi hostning kodi ThreadAbortException ni ushlaganligi sababli, host mijozga biror xatolik javobini qaytarishi va oqimlar puli oqimini kelajakdagi mijoz so'rovi uchun pulga qaytarishga ruxsat berishi mumkin.

Thread ning Abort metodi asinxrondir. Abort chaqirilganda, u maqsad oqimning AbortRequested bayrog'ini o'rnatadi va darhol qaytadi. Runtime oqim to'xtatilishi kerakligini aniqlaganda, u oqimni xavfsiz joyga olib kelishga harakat qiladi. Xavfsiz joy — bu runtime oqimni halokatli oqibatlarsiz to'xtata oladigan joy.

Oqim xavfsiz joyga yetganda, runtime AbortRequested bayrog'ining o'rnatilganligini aniqlaydi. Bu oqimga ThreadAbortException chiqaradi. Agar bu istisno ushlangmasa, istisno qayta ishlanmagan bo'lib, barcha kutilayotgan finally bloklar bajariladi va oqim o'zini engil tarzda tugatadi.

CLR ThreadAbortException ni juda maxsus tarzda ko'rib chiqadi. Kod ThreadAbortException ni ushlasa ham, CLR istisnoni yutishga ruxsat bermaydi. Boshqacha aytganda, catch bloki oxirida CLR avtomatik ravishda ThreadAbortException ni qayta chiqaradi.

CLR ning ushbu xususiyati boshqa savolni tug'diradi: agar CLR catch bloki oxirida ThreadAbortException ni qayta chiqarsa, hostning catch bloki oqim boshqaruvini qayta olish uchun qanday ushlashi mumkin? Hostning catch bloki ichida Thread ning ResetAbort metodiga chaqiruv bor. Ushbu metodni chaqirish CLR ga har bir catch bloki oxirida ThreadAbortException ni qayta chiqarishni to'xtatishni aytadi.

Bu yana boshqa savolni tug'diradi: ishonchsiz kodni ThreadAbortException ni ushlash va Thread ning ResetAbort metodini o'zi chaqirib oqim boshqaruvini saqlab qolishdan nima to'sqinlik qiladi? Javob shuki, Thread ning ResetAbort metodi chaqiruvchining ControlThread bayrog'i true ga o'rnatilgan SecurityPermission ga ega bo'lishini talab qiladi. Host ishonchsiz kod uchun AppDomain yaratganda, host bu ruxsatni bermaydi va endi ishonchsiz kod hostning oqimi ustidan boshqaruvni saqlab qola olmaydi.

Amaliy eslatma

Shuni ta'kidlash kerakki, ko'pchilik ishonchsiz kodlar aslida zararli bo'lishga mo'ljallanmagan; u shunchaki hostning standartlariga ko'ra juda uzoq bajarilishi kerak bo'lgan tarzda yozilgan. Odatda catch va finally bloklari juda kam kod o'z ichiga oladi va bu kod odatda cheksiz halqalar yoki uzoq davom etuvchi vazifalar bo'lmagan holda tezda bajariladi. Shuning uchun eskalatsiya siyosatining hostga o'z oqimi boshqaruvini qayta olish uchun amal qilishi juda kam ehtimolga ega.

Aytgancha, Thread sinfi aslida ikkita Abort metodini taklif qiladi: biri parametrsiz va ikkinchisi sizga ixtiyoriy narsani uzatish imkonini beruvchi Object parametri bilan. Kod ThreadAbortException ni ushlaganda, u faqat-o'qish ExceptionState xususiyatini so'rashi mumkin. Bu xususiyat Abort ga uzatilgan obyektni qaytaradi. Bu Abort ni chaqirayotgan oqimga qo'shimcha ma'lumot belgilash imkonini beradi. Host buni ThreadAbortException ni ushlaydigan o'z kodi oqimlarni nima uchun to'xtatayotganini bilishi uchun ishlatishi mumkin.