有没有办法强制卸载 AssemblyLoadContext?

Láz*_*olt 5 c# asp.net appdomain .net-assembly asp.net-core

我正在开发一个大型 ASP.Net 5 Web 应用程序,我想实现自我更新功能。由于程序可以以不同的方式部署在许多不同的平台上(例如Windows服务,Linux上的systemd服务,Docker容器等......),实现自我更新机制的唯一可行的方法是编写一个主机进程可以加载和卸载主程序DLL及其依赖项。卸载很重要,因为主机程序(更新程序)必须能够在运行时覆盖服务器的 DLL 文件。

以下是我到目前为止所做的工作:我已将主 Web 应用程序的输出类型更改为 Library,并且制作了一个小型加载程序,该程序将该 DLL 及其依赖项加载到 中HostAssemblyLoadContext,然后调用原始Main()方法。服务器应用程序被编程为在启动后几秒钟正常关闭,因此该CreateHostBuilder(args).Build().Run()方法Main()可以返回。之后,我尝试调用HostAssemblyLoadContext.Unload(),但由于某种原因,加载上下文拒绝实际卸载。

这是我的 HostAssemblyLoadContext 实现:

public class HostAssemblyLoadContext : AssemblyLoadContext
{
    private readonly AssemblyDependencyResolver _resolver;

    public HostAssemblyLoadContext(string pluginPath) : base(isCollectible: true)
    {
        _resolver = new AssemblyDependencyResolver(pluginPath);
    }

    protected override Assembly? Load(AssemblyName name)
    {
        string? assemblyPath = _resolver.ResolveAssemblyToPath(name);
        if (assemblyPath != null)
            return LoadFromAssemblyPath(assemblyPath);

        string filePath = $"{name.FullName.Split(',')[0]}.dll";
        if(File.Exists(filePath))
            return Assembly.LoadFrom(filePath);

        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

我的加载器代码:

[MethodImpl(MethodImplOptions.NoInlining)]
private static void ExecuteMainMethod(string[] args, string assemblyFileName, out WeakReference contextReference, out HostAssemblyLoadContext loadContext)
{
    loadContext = new HostAssemblyLoadContext(new DirectoryInfo(".").FullName);
    contextReference = new WeakReference(loadContext);

    var assembly = loadContext.LoadFromAssemblyPath(assemblyFileName);
    var mainMethod = FindMainMethod(assembly)!;

    try
    {
        mainMethod.Invoke(null, new object?[] {args});
    }
    catch
    {
        // ignore
    }
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static void Unload(WeakReference weakReference, ref HostAssemblyLoadContext loadContext)
{
    loadContext.Unload(); 
    loadContext = null;

    for (int i = 0; weakReference.IsAlive && i < 100; i++)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

    Console.WriteLine($"is alive: {weakReference.IsAlive}");
}

public static void Main(string[] args)
{
    ExecuteMainMethod(args, new FileInfo("FirestormSW.SmartGrade.dll").FullName, out var weakReference, out var loadContext);
    Unload(weakReference, ref loadContext);
        
    Console.Out.WriteLine("Press ENTER to exit");
    Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)

以及实际ASP服务器的主要方法:

public static void Main(string[] args)
{
    Console.Out.WriteLine("Starting Server...");
    CreateHostBuilder(args).Build().Run();
    Console.Out.WriteLine("Server stopped.");
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果我将此 Main 方法的内容替换为 simple Thread.Sleep(1000),则上下文会成功卸载。

当我运行该程序并等待服务器自行关闭时,我在控制台上看到的是:

Starting Server...
Shutting down...
is alive: True
Run Code Online (Sandbox Code Playgroud)

这意味着服务器的 main 方法已经返回,但某些东西仍然保持加载上下文处于活动状态。我查看了Process.GetCurrentProcess().Threads.Count服务器启动/停止之前和之后的线程计数 ( ),数字从 8 跳到了 27。这使我假设上下文由 ASP 创建的一些线程保持活动状态。 Net 应用程序,但我不确定。如果是这种情况,我不知道如何找出哪些线程负责,即使我可以,我也不确定是否可以中止它们。