Informatie over System.Runtime.Loader.AssemblyLoadContext
De AssemblyLoadContext klasse is geïntroduceerd in .NET Core en is niet beschikbaar in .NET Framework. Dit artikel is een aanvulling op de AssemblyLoadContext API-documentatie met conceptuele informatie.
Dit artikel is relevant voor ontwikkelaars die dynamisch laden implementeren, met name frameworkontwikkelaars voor dynamisch laden.
Wat is AssemblyLoadContext?
Elke .NET 5+ en .NET Core-toepassing maakt impliciet gebruik van AssemblyLoadContext. Het is de provider van de runtime voor het zoeken en laden van afhankelijkheden. Wanneer een afhankelijkheid wordt geladen, wordt een AssemblyLoadContext exemplaar aangeroepen om deze te vinden.
Het biedt een service voor het zoeken, laden en opslaan van beheerde assembly's en andere afhankelijkheden.
Ter ondersteuning van dynamisch laden en lossen van code wordt er een geïsoleerde context gemaakt voor het laden van code en de bijbehorende afhankelijkheden in hun eigen AssemblyLoadContext exemplaar.
Wanneer hebt u meerdere AssemblyLoadContext-exemplaren nodig?
Eén AssemblyLoadContext exemplaar is beperkt tot het laden van precies één versie van een Assembly per eenvoudige assemblynaam, AssemblyName.Name.
Deze beperking kan een probleem worden bij het dynamisch laden van codemodules. Elke module wordt onafhankelijk gecompileerd en kan afhankelijk zijn van verschillende versies van een Assembly. Dit probleem treedt meestal op wanneer verschillende modules afhankelijk zijn van verschillende versies van een veelgebruikte bibliotheek.
Ter ondersteuning van dynamisch laden van code biedt de AssemblyLoadContext API het laden van conflicterende versies van een Assembly in dezelfde toepassing. Elk AssemblyLoadContext exemplaar biedt een unieke woordenlijsttoewijzing die elk AssemblyName.Name aan een specifiek exemplaar is toegewezen Assembly .
Het biedt ook een handig mechanisme voor het groeperen van afhankelijkheden met betrekking tot een codemodule voor later verwijderen.
Wat is er speciaal aan de AssemblyLoadContext.Default-instantie?
Het AssemblyLoadContext.Default exemplaar wordt automatisch ingevuld door de runtime bij het opstarten. Hierbij wordt standaard gezocht naar alle statische afhankelijkheden en deze te vinden.
Hiermee worden de meest voorkomende scenario's voor het laden van afhankelijkheden opgelost.
Hoe ondersteunt AssemblyLoadContext dynamische afhankelijkheden?
AssemblyLoadContext heeft verschillende gebeurtenissen en virtuele functies die kunnen worden overschreven.
Het AssemblyLoadContext.Default exemplaar ondersteunt alleen het overschrijven van de gebeurtenissen.
De artikelen Managed assembly loading algorithm, Satellite assembly loading algorithm, and Unmanaged (native) library loading algorithm refereren naar alle beschikbare gebeurtenissen en virtuele functies. In de artikelen worden de relatieve positie van elke gebeurtenis en functie in de laadalgoritmen weergegeven. In dit artikel worden deze gegevens niet gereproduceerd.
In deze sectie worden de algemene principes voor de relevante gebeurtenissen en functies beschreven.
- Herhaalbaar zijn. Een query voor een specifieke afhankelijkheid moet altijd resulteren in hetzelfde antwoord. Hetzelfde geladen afhankelijkheidsexemplaren moeten worden geretourneerd. Deze vereiste is essentieel voor cacheconsistentie. Voor beheerde assembly's wordt met name een Assembly cache gemaakt. De cachesleutel is een eenvoudige assemblynaam. AssemblyName.Name
- Gooi meestal niet. Er wordt verwacht dat deze functies worden geretourneerd
nullin plaats van te gooien wanneer de aangevraagde afhankelijkheid niet kan worden gevonden. Als u de zoekopdracht voortijdig genereert, wordt de zoekopdracht beëindigd en wordt een uitzondering doorgegeven aan de aanroeper. Het gooien moet worden beperkt tot onverwachte fouten, zoals een beschadigde assembly of een onvoldoende geheugenvoorwaarde. - Vermijd recursie. Houd er rekening mee dat deze functies en handlers de laadregels implementeren voor het vinden van afhankelijkheden. Uw implementatie mag geen API's aanroepen die recursie activeren. Uw code moet doorgaans AssemblyLoadContext-laadfuncties aanroepen waarvoor een specifiek pad of een verwijzingsargument voor geheugen is vereist.
- Laad in de juiste AssemblyLoadContext. De keuze waar afhankelijkheden moeten worden geladen, is toepassingsspecifiek. De keuze wordt geïmplementeerd door deze gebeurtenissen en functies. Wanneer uw code AssemblyLoadContext load-by-path-functies aanroept, worden deze aangeroepen op het exemplaar waar u de code wilt laden. Soms is het retourneren
nullen het afhandelen van de AssemblyLoadContext.Default belasting mogelijk de eenvoudigste optie. - Houd rekening met threadraces. Laden kan worden geactiveerd door meerdere threads. AssemblyLoadContext verwerkt threadraces door atomisch assembly's toe te voegen aan de cache. Het exemplaar van de race-loser wordt verwijderd. Voeg in uw implementatielogica geen extra logica toe die meerdere threads niet goed verwerkt.
Hoe worden dynamische afhankelijkheden geïsoleerd?
Elk AssemblyLoadContext exemplaar vertegenwoordigt een uniek bereik voor Assembly exemplaren en Type definities.
Er is geen binaire isolatie tussen deze afhankelijkheden. Ze zijn alleen geïsoleerd door elkaar niet op naam te vinden.
In elk AssemblyLoadContext:
- AssemblyName.Name kan verwijzen naar een ander Assembly exemplaar.
- Type.GetType kan een ander typeexemplaren retourneren voor hetzelfde type
name.
Hoe worden afhankelijkheden gedeeld?
Afhankelijkheden kunnen eenvoudig worden gedeeld tussen AssemblyLoadContext instanties. Het algemene model is bedoeld om AssemblyLoadContext een afhankelijkheid te laden. De andere deelt de afhankelijkheid met behulp van een verwijzing naar de geladen assembly.
Dit delen is vereist voor de runtime-assembly's. Deze assembly's kunnen alleen in de AssemblyLoadContext.Default. Hetzelfde is vereist voor frameworks zoals ASP.NET, WPFof WinForms.
Het wordt aanbevolen om gedeelde afhankelijkheden in AssemblyLoadContext.Defaultte laden. Dit delen is het algemene ontwerppatroon.
Delen wordt geïmplementeerd in de codering van het aangepaste AssemblyLoadContext exemplaar. AssemblyLoadContext heeft verschillende gebeurtenissen en virtuele functies die kunnen worden overschreven. Wanneer een van deze functies een verwijzing retourneert naar een exemplaar dat in een Assembly ander AssemblyLoadContext exemplaar is geladen, wordt het Assembly exemplaar gedeeld. Het standaard load-algoritme wordt uitgesteld tot AssemblyLoadContext.Default laden om het algemene patroon voor delen te vereenvoudigen. Zie het algoritme voor het laden van beheerde assembly's voor meer informatie.
Complicaties
Problemen met typeconversie
Wanneer twee AssemblyLoadContext exemplaren typedefinities met hetzelfde namebevatten, zijn ze niet hetzelfde type. Ze zijn hetzelfde type als en alleen als ze afkomstig zijn van hetzelfde Assembly exemplaar.
Om zaken ingewikkeld te maken, kunnen uitzonderingsberichten over deze niet-overeenkomende typen verwarrend zijn. De typen worden aangeduid in de uitzonderingsberichten op basis van hun eenvoudige typenamen. Het algemene uitzonderingsbericht in dit geval is van het formulier:
Het object van het type IsolatedType kan niet worden geconverteerd naar het type IsolatedType.
Problemen met foutopsporingstypeconversie
Gezien een paar niet-overeenkomende typen, is het belangrijk om ook te weten:
- Elk type is Type.Assembly.
- Elk type AssemblyLoadContext, dat kan worden verkregen via de AssemblyLoadContext.GetLoadContext(Assembly) functie.
Gezien twee objecten a en bis het evalueren van het volgende in het foutopsporingsprogramma nuttig:
// In debugger look at each assembly's instance, Location, and FullName
a.GetType().Assembly
b.GetType().Assembly
// In debugger look at each AssemblyLoadContext's instance and name
System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(a.GetType().Assembly)
System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(b.GetType().Assembly)
Problemen met typeconversie oplossen
Er zijn twee ontwerppatronen voor het oplossen van deze typeconversieproblemen.
Algemene gedeelde typen gebruiken. Dit gedeelde type kan een primitief runtimetype zijn of het kan betekenen dat u een nieuw gedeeld type maakt in een gedeelde assembly. Vaak is het gedeelde type een interface die is gedefinieerd in een toepassingsassembly. Lees voor meer informatie hoe afhankelijkheden worden gedeeld.
Gebruik marshallingtechnieken om van het ene type naar het andere te converteren.