【问题标题】:What is the value of AssemblyLoadContext.Unload() in .NET Core in comparison with regular garbage collection?与常规垃圾回收相比,.NET Core 中的 AssemblyLoadContext.Unload() 的值是多少?
【发布时间】:2020-06-01 07:56:15
【问题描述】:

.NET Core 3.0 引入了可收集的AssemblyLoadContext,它允许调用Unload() 方法来卸载上下文中加载的程序集。

根据文档 (https://docs.microsoft.com/en-us/dotnet/standard/assembly/unloadability#troubleshoot-unloadability-issues),卸载是异步的,任何对上下文或对象的引用都会阻止上下文卸载。

我想知道如果我失去对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 个 GC 会话后卸载上下文,无论没有 Unload() 是否需要 2 个会话才能卸载。但可能只是巧合,并不总是可重现的。

所以,考虑到这一点

  1. 任何对可收集上下文的引用都会阻止它卸载(因此可以在加载所有需要安排卸载的程序集之后调用Unload(),以便在不使用时调度卸载)。
  2. 即使没有调用 Unload() 可收集的上下文,一旦不再使用,它​​也会被卸载。

这个Unload()方法的目的是什么,使用Unload()和单纯依靠GC有什么区别?

【问题讨论】:

  • 这看起来和“Dispose()方法的目的是什么以及使用Dispose()和单纯依靠GC有什么区别?”完全一样的问题
  • @LasseV.Karlsen 不是真的。 Dispose 通常是同步的,这里没有安排为 Unload。我可以在将所有东西加载到它之后调用 Unload,无论如何我将不得不等待 GC 来收集它(并且在使用上下文时它不会)。我不能遵循相同的 Dispose 模式。

标签: c# .net .net-core assembly-loading dynamic-assemblies


【解决方案1】:

我有同样的印象,我所做的测试也显示了同样的结果。显式调用Unload() 似乎没有意义。然后我找到https://github.com/dotnet/samples/blob/master/core/tutorials/Unloading 并意识到如果您在那里评论Unload(),则不会卸载该库。这是https://github.com/dotnet/samples/blob/master/core/tutorials/Unloading/Host/Program.cs#L74 行。 最终就像 Lasse V. Karlsen 所说的那样。如果您显式调用Unload(),则可以预期您的库将被更快地卸载。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-01-17
    • 2017-10-23
    • 2010-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-02
    相关资源
    最近更新 更多