C# .NET Core 3.1 中 AssemblyLoadContext 的基本使用

发布时间:2022-07-05 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了C# .NET Core 3.1 中 AssemblyLoadContext 的基本使用脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

C# .NET Core 3.1 中 AsSEMblyLoadContext 的基本使用

前言

之前使用 AppDomain 写过一个动态加载和释放程序的案例,基本实现了自己“兔死狗烹”,不留痕迹的设想。无奈在最新的 .NET Core 3.1 中,已经不支持创建新的 AppDomain 了(据说是因为跨平台实现太重了),改为使用 AssemblyLoadContext 了。不过总体使用下来感觉比原来的 AppDomain 要直观。

不过这一路查找资料,感觉 .NET Core 发展到 3.1 的过程还是经历了不少的。比如 2.2 的 API 与 3.1 就不一样(自己的体会,换了个版本就提示函数参数错误), preview版中 AssemblyLoadContext 卸载后无法删除库文件,但是版本升级后就好了(gIThub 上的一篇讨论)

本文主要是关于 AssemblyLoadContext 的基本使用,加载和释放类库。

基本使用

程序的基本功能是:动态加载 Magick 的所需库,并调用其压缩图片的函数压缩给定图片。(歪个楼,Magick 和 AndROId 的 Magisk 这两个看起来太像了)

using System;
using System.IO;
using System.Reflection;
using System.Runtime@R_126_1718@pilerServices;
using System.Runtime.Loader;

namespace AssemblyLoadContexttest
{
    class PRogram
    {
        static void Main(string[] args)
        {
            WeakReference weakReference;

            ComPress(out weakReference);

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

            Console.WriteLine($"卸载成功: {!weakReference.IsAlive}");
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static void Compress(out WeakReference weakReference)
        {
            AssemblyLoadContext alc = new AssemblyLoadContext("CompressLibrary", true);	// 新建一个 AssemblyLoadContext 对象

            weakReference = new WeakReference(alc);

            Assembly assembly0 = alc.LoadFromAssemblyPath(Path.Combine(AppDomain.currentDomain.BaseDirectory, "Magick.NET.Core.dll"));
            Assembly assembly1 = alc.LoaDFromAssemblyPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Magick.NET-Q16-AnyCPU.dll"));
            
            string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "image_to_compress.jpg");

            Console.WriteLine("压缩前大小:" + new Fileinfo(filePath).Length);

            VAR magickImageType = assembly1.GetType("ImageMagick.MagickImage"); // 已知该类定义在 assembly1 中
            var magickImageIns = Activator.CreateInstance(magickImageType, new object[] { filePath });  // magickImageIns = new ImageMagick.MagickImage(filePath)
            var qualityProperty = magickImageType.GetProperty("Quality");
            qualityProperty.SetValue(magickImageIns, 60);   // magickImageIns.Quality = 60
            var writeMethod = magickImageType.GetMethod("Write", new Type[] { typeof(string) });
            writeMethod.Invoke(magickImageIns, new object[] { filePath }); // magickImageIns.Write(filePath)

            Console.WriteLine("压缩后大小:" + new FileInfo(filePath).Length);

            var disposeMethod = magickImageType.GetMethod("Dispose");
            disposeMethod.Invoke(magickImageIns, null); // magickImageIns.Dispose()

            //magickImageIns = null;
            alc.Unload();
        }
    }
}

C# .NET Core 3.1 中 AssemblyLoadContext 的基本使用

加载不用多说,创建实例加载即可;卸载时需要注意的是一下几点:

  1. 使用 AssemblyLoaderContext 加载和卸载的代码必须要单独放在一个方法,不可以写在 Main 方法中,否则加载的模块只有等待整个程序退出后才能卸载
  2. 方法中应加上 [MethodImpl(MethodImplOptions.NoInlining)] 特性,否则可能也不会正常卸载(在本例子中似乎不加也可以),官方示例是这么说的:

It is important to mark this method as NoInlining, otherwise the JIT could decide to inline it into the Main method. That could then prevent successful unloading of the plugin because some of the MethodInfo / Type / Plugin.Interface / HostAssemblyLoadContext instances may get lifetime extended beyond the point when the plugin is expected to be unloaded.

  1. 卸载的过程是异步的,调用了以后并不会立刻完成
  2. 如果一定要等待其完成可以通过创建一个 WeakReference 指向它,通过查看 WeakReference 是否存在来判断是否完成释放。 但等待释放的方法要在“加载卸载的代码”方法外,否则依然无法查看到它被回收
  3. 还有一点比较奇怪,如果我在最后不加 magickImageIns = null; 这一句,有时可以卸载,有时又无法卸载。如果类似的情况无法卸载,可以加上试试。

TIPS

Visual Studio 中提供了“模块窗口”,可以及时查看加载了哪些程序集,在 “调试” > “窗口” > “模块”

C# .NET Core 3.1 中 AssemblyLoadContext 的基本使用

简单对比 AppDomain

AppDomain 似乎是一个大而全的概念,包括了程序运行的方方面面:工作路径、引用搜索路径、配置文件、卷影复制 等,而 AssemblyLoadContext 只是一个加载程序集的工具。

参考

官方示例(参看其中的 /Host/Program.cs)

Visual Studio 中的 模块 窗口 https://docs.microsoft.com/zh-cn/visualstudio/debugger/how-to-use-the-modules-window?view=vs-2019

这篇挺详细的,很多问题我没有深入地研究,但是其中的“需要的变量放到静态字典中.在Unload之前把对应的Key值删除掉”我不认同,也可能是因为版本原因吧 https://www.cnblogs.com/LucasDot/p/13956384.htML

提问者无意间通过 ref 引用了 AssemblyLoadContext 对象而导致无法回收 https://stackoverflow.com/questions/55693269/assemblyloadcontext-did-not-unload-correctly

最后的测试方法应该单独写在一个方法中而不是在 Main 函数中(作者没有显式指明,我在这困扰了好久) https://www.cnbLOGs.com/maxzhang1985/p/10875278.html

脚本宝典总结

以上是脚本宝典为你收集整理的C# .NET Core 3.1 中 AssemblyLoadContext 的基本使用全部内容,希望文章能够帮你解决C# .NET Core 3.1 中 AssemblyLoadContext 的基本使用所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。