与常规垃圾 collection 相比,.NET Core 中 AssemblyLoadContext.Unload() 的价值是多少?
What is the value of AssemblyLoadContext.Unload() in .NET Core in comparison with regular garbage collection?
.NET Core 3.0 引入了可收集的 AssemblyLoadContext
,它允许调用 Unload()
方法来卸载在上下文中加载的程序集。
根据文档 (https://docs.microsoft.com/en-us/dotnet/standard/assembly/unloadability#troubleshoot-unloadability-issues),卸载是异步的,任何对上下文或objects的引用都会阻止上下文卸载。
我想知道如果我丢失了对 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次GCsession后卸载上下文,是否没有Unload()
需要2sessions来卸载。但可能只是巧合,并不总是可重现的。
所以,鉴于
- 任何对可收集上下文的引用都会阻止它卸载(因此可以在加载所有需要安排卸载的程序集后立即调用
Unload()
,当它不被使用时)。
- 即使不调用
Unload()
可收集上下文一旦不再使用也会被卸载。
这个 Unload()
方法的目的是什么?使用 Unload()
和简单地依赖 GC 有什么区别?
我有同样的印象,我做的测试也显示了同样的结果。显式调用 Unload()
似乎没有意义。然后我找到了https://github.com/dotnet/samples/blob/master/core/tutorials/Unloading and realized that if you comment Unload()
there, the lib won't be unloaded. This is the line https://github.com/dotnet/samples/blob/master/core/tutorials/Unloading/Host/Program.cs#L74。
最终就像 Lasse V. Karlsen 所说的那样。如果您显式调用 Unload()
,您可以预期您的库将被更快地卸载。
.NET Core 3.0 引入了可收集的 AssemblyLoadContext
,它允许调用 Unload()
方法来卸载在上下文中加载的程序集。
根据文档 (https://docs.microsoft.com/en-us/dotnet/standard/assembly/unloadability#troubleshoot-unloadability-issues),卸载是异步的,任何对上下文或objects的引用都会阻止上下文卸载。
我想知道如果我丢失了对 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次GCsession后卸载上下文,是否没有Unload()
需要2sessions来卸载。但可能只是巧合,并不总是可重现的。
所以,鉴于
- 任何对可收集上下文的引用都会阻止它卸载(因此可以在加载所有需要安排卸载的程序集后立即调用
Unload()
,当它不被使用时)。 - 即使不调用
Unload()
可收集上下文一旦不再使用也会被卸载。
这个 Unload()
方法的目的是什么?使用 Unload()
和简单地依赖 GC 有什么区别?
我有同样的印象,我做的测试也显示了同样的结果。显式调用 Unload()
似乎没有意义。然后我找到了https://github.com/dotnet/samples/blob/master/core/tutorials/Unloading and realized that if you comment Unload()
there, the lib won't be unloaded. This is the line https://github.com/dotnet/samples/blob/master/core/tutorials/Unloading/Host/Program.cs#L74。
最终就像 Lasse V. Karlsen 所说的那样。如果您显式调用 Unload()
,您可以预期您的库将被更快地卸载。