Есть ли способ заставить выгрузить AssemblyLoadContext?C#

Место общения программистов C#
Anonymous
Есть ли способ заставить выгрузить AssemblyLoadContext?

Сообщение Anonymous »

Я работаю над большим веб-приложением ASP.Net 5 и хотел бы реализовать функцию самостоятельного обновления. Поскольку программу можно развернуть на многих разных платформах и разными способами (например, служба Windows, служба systemd в Linux, контейнер Docker и т. д.), единственный реальный способ реализовать механизм самообновления — это написать хост-процесс, который может загружать и выгружать основную DLL программы и ее зависимости. Выгрузка важна, поскольку хост-программа (программа обновления) должна иметь возможность перезаписывать DLL-файлы сервера во время работы.
Вот что я сделал до сих пор: я изменил тип вывода основного веб-приложения на «Библиотека» и создал небольшую программу-загрузчик, которая загружает эту 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;
}
}
Мой код загрузчика:

Код: Выделить всё

[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();
}
И основной метод настоящего ASP-сервера:

Код: Выделить всё

public static void Main(string[] args)
{
Console.Out.WriteLine("Starting Server...");
CreateHostBuilder(args).Build().Run();
Console.Out.WriteLine("Server stopped.");
}
Обратите внимание, что если я заменю содержимое этого метода Main простым Thread.Sleep(1000), контекст успешно выгрузится.
Когда я запускаю эту программу и жду, пока сервер выключится, вот что я вижу на консоли:

Код: Выделить всё

Starting Server...
Shutting down...
is alive: True
Это означает, что основной метод сервера вернулся, но что-то все еще поддерживает контекст загрузки. Я посмотрел количество потоков (

Код: Выделить всё

Process.GetCurrentProcess().Threads.Count
) до и после запуска/остановки сервера, а число увеличивается с 8 до 27. Это заставляет меня предположить, что контекст поддерживается некоторыми потоками, созданными приложением ASP.Net, но я не уверен. А если это так, то я не знаю, как узнать, какие потоки ответственны за это, и даже если бы я мог, я не уверен, можно ли их прервать.

Вернуться в «C#»