C# .NET Core 3.1 中 AssemblyLoadContext 的基本使用
<h2 id="c-net-core-31-中-assemblyloadcontext-的基本使用">C# .NET Core 3.1 中 AssemblyLoadContext 的基本使用</h2><h3 id="前言">前言</h3>
<p>之前使用 <strong>AppDomain</strong> 写过一个动态加载和释放程序的案例,基本实现了自己“兔死狗烹”,不留痕迹的设想。无奈在最新的 .NET Core 3.1 中,已经不支持创建新的 AppDomain 了(据说是因为跨平台实现太重了),改为使用 <strong>AssemblyLoadContext</strong> 了。不过总体使用下来感觉比原来的 AppDomain 要直观。</p>
<p>不过这一路查找资料,感觉 .NET Core 发展到 3.1 的过程还是经历了不少的。比如 2.2 的 API 与 3.1 就不一样(自己的体会,换了个版本就提示函数参数错误), preview版中 AssemblyLoadContext 卸载后无法删除库文件,但是版本升级后就好了(github 上的一篇讨论)</p>
<p>本文主要是关于 AssemblyLoadContext 的基本使用,加载和释放类库。</p>
<h3 id="基本使用">基本使用</h3>
<p>程序的基本功能是:动态加载 Magick 的所需库,并调用其压缩图片的函数压缩给定图片。<em>(歪个楼,Magick 和 Android 的 Magisk 这两个看起来太像了)</em></p>
<pre><code>using System;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
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}");
}
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();
}
}
}
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/1611896/202109/1611896-20210917195653830-80563749.png" alt="" loading="lazy"></p>
<p>加载不用多说,创建实例加载即可;卸载时需要注意的是一下几点:</p>
<ol>
<li><strong>使用 AssemblyLoaderContext 加载和卸载的代码必须要单独放在一个方法,不可以写在 Main 方法中</strong>,否则加载的模块只有等待整个程序退出后才能卸载</li>
<li>方法中应加上 <strong></strong> 特性,否则可能也不会正常卸载(在本例子中似乎不加也可以),官方示例是这么说的:</li>
</ol>
<blockquote>
<p>It is important to mark this method as NoInlining, otherwise the JIT could decide<br>
to inline it into the Main method. That could then prevent successful unloading<br>
of the plugin because some of the MethodInfo / Type / Plugin.Interface / HostAssemblyLoadContext<br>
instances may get lifetime extended beyond the point when the plugin is expected to be<br>
unloaded.</p>
</blockquote>
<ol start="3">
<li><strong>卸载的过程是异步的</strong>,调用了以后并不会立刻完成</li>
<li>如果一定要等待其完成可以通过创建一个 WeakReference 指向它,<strong>通过查看 WeakReference 是否存在来判断是否完成释放</strong>。 但等待释放的方法要在“加载卸载的代码”方法外,否则依然无法查看到它被回收</li>
<li>还有一点比较奇怪,如果我在最后不加 magickImageIns = null; 这一句,有时可以卸载,有时又无法卸载。如果类似的情况无法卸载,可以加上试试。</li>
</ol>
<h3 id="tips">TIPS</h3>
<p>在 Visual Studio 中提供了“模块窗口”,可以及时查看加载了哪些程序集,在 <strong>“调试” > “窗口” > “模块”</strong></p>
<p><img src="https://img2020.cnblogs.com/blog/1611896/202109/1611896-20210917195707633-2092969771.png" alt="" loading="lazy"></p>
<h3 id="简单对比-appdomain">简单对比 AppDomain</h3>
<p>AppDomain 似乎是一个大而全的概念,包括了程序运行的方方面面:工作路径、引用搜索路径、配置文件、卷影复制 等,而 AssemblyLoadContext 只是一个加载程序集的工具。</p>
<h3 id="参考">参考</h3>
<p>官方示例(参看其中的 /Host/Program.cs)</p>
<p>Visual Studio 中的 模块 窗口<br>
https://docs.microsoft.com/zh-cn/visualstudio/debugger/how-to-use-the-modules-window?view=vs-2019</p>
<p>这篇挺详细的,很多问题我没有深入地研究,但是其中的<em>“需要的变量放到静态字典中.在Unload之前把对应的Key值删除掉”</em>我不认同,也可能是因为版本原因吧<br>
https://www.cnblogs.com/LucasDot/p/13956384.html</p>
<p>提问者无意间通过 ref 引用了 AssemblyLoadContext 对象而导致无法回收<br>
https://stackoverflow.com/questions/55693269/assemblyloadcontext-did-not-unload-correctly</p>
<p>最后的测试方法应该单独写在一个方法中而不是在 Main 函数中(作者没有显式指明,我在这困扰了好久)<br>
https://www.cnblogs.com/maxzhang1985/p/10875278.html</p><br><br>
来源:https://www.cnblogs.com/battor/p/csharp_dotnet31_assemblyloadcontext_simple_usage.html
頁:
[1]