. NET В Core 3.0 представлен коллекционный AssemblyLoadContext
, который позволяет вызывать метод Unload()
для выгрузки сборок, загруженных внутри контекста.
Согласно документации (https://docs.microsoft.com/en-us/dotnet/standard/assembly/unloadability#troubleshoot -unloadability- выдает ), выгрузка асинхронная, и любая ссылка на контекст или объекты из нее будут препятствовать выгрузке контекста.
Мне было интересно, что если я потеряю ссылку на AssemblyLoadContext
, это приведет к утечке (поскольку у меня больше нет контекста для вызова Unload()
). Тест подтвердил, что это не приведет к утечке, и неиспользуемая сборка будет выгружена даже без явного вызова Unload()
:
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using NUnit.Framework;
namespace Tests.Core
{
[TestFixture]
public class CollectibleAssemblyLoadContextTests
{
private const string AssemblyName = "Test___DynamicAssembly";
[Test]
[TestCase(/*unload*/ true, /*GC sessions*/ 1)]
[TestCase(/*unload*/ false, /*GC sessions*/ 2)]
public void ShouldExecuteAndUnload(bool unload, int expectedGcSessions)
{
string actual = Execute(10, unload);
Assert.AreEqual("executed 10", actual);
int gcSessions = 0;
while (!IsUnloaded())
{
GC.Collect();
gcSessions++;
}
Assert.AreEqual(expectedGcSessions, gcSessions);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private bool IsUnloaded()
{
return !AppDomain.CurrentDomain.GetAssemblies()
.Select(x => x.GetName().Name)
.Contains(AssemblyName);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private string Execute(int number, bool unload)
{
var source = @"
public static class Process
{
public static string Execute(int i)
{
return $""executed {i}"";
}
}";
var compilation = CSharpCompilation.Create(AssemblyName, new[] {CSharpSyntaxTree.ParseText(source)},
new []{MetadataReference.CreateFromFile(typeof(object).Assembly.Location)},
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using var ms = new MemoryStream();
compilation.Emit(ms);
ms.Seek(0, SeekOrigin.Begin);
var assemblyLoadContext = new AssemblyLoadContext("CollectibleContext", isCollectible: true);
Assembly assembly = assemblyLoadContext.LoadFromStream(ms);
if (unload)
assemblyLoadContext.Unload();
Type type = assembly.GetType("Process");
MethodInfo method = type.GetMethod("Execute");
return (string)method.Invoke(null, new object[] {number});
}
}
}
Этот тест также показывает, что при использовании Unload()
контекст выгружается после 1 G C сеанс, без Unload()
требуется 2 сеанса для выгрузки. Но может быть просто совпадением и не всегда воспроизводимым.
Итак, учитывая, что
- Любые ссылки на коллекционный контекст будут препятствовать выгрузке формы (поэтому можно просто вызвать
Unload()
после загрузки всех сборок необходимо запланировать выгрузку, когда она не будет использоваться). - Даже без вызова
Unload()
коллекционный контекст выгружается, когда он больше не используется.
Какова цель этого Unload()
метода и в чем разница между использованием Unload()
и простым использованием G C?