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.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}");
} [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();
}
}
}

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

  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 中提供了“模块窗口”,可以及时查看加载了哪些程序集,在 “调试” > “窗口” > “模块”

简单对比 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 的基本使用的更多相关文章

  1. 使用 .NET Core 3.0 的 AssemblyLoadContext 实现插件热加载

    一般情况下,一个 .NET 程序集加载到程序中以后,它的类型信息以及原生代码等数据会一直保留在内存中,.NET 运行时无法回收它们,如果我们要实现插件热加载 (例如 Razor 或 Aspx 模版的热 ...

  2. .net core 3.0中动态卸载程序集

    动态加载程序集在一些插件式的应用中非常常见,.net core 2.0中可以通过AssemblyLoadContext中提供程序集的动态加载功能,但取不支持卸载.现在,在.net core 3.0中提 ...

  3. ASP.NET Core HTTP 管道中的那些事儿

    前言 马上2016年就要过去了,时间可是真快啊. 上次写完 Identity 系列之后,反响还不错,所以本来打算写一个 ASP.NET Core 中间件系列的,但是中间遇到了很多事情.首先是 NPOI ...

  4. 在.NET Core控制台程序中使用依赖注入

    之前都是在ASP.NET Core中使用依赖注入(Dependency Injection),昨天遇到一个场景需要在.NET Core控制台程序中使用依赖注入,由于对.NET Core中的依赖注入机制 ...

  5. ASP.NET Core 1.0 中的依赖项管理

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

  6. 在ASP.NET Core 1.0中如何发送邮件

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:目前.NET Core 1.0中并没有提供SMTP相关的类库,那么要如何从ASP.NE ...

  7. EF Core 1.0中使用Include的小技巧

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:由于EF Core暂时不支持Lazy Loading,所以利用Include来加载额外 ...

  8. ASP.NET Core 1.0 中使用 Swagger 生成文档

    github:https://github.com/domaindrivendev/Ahoy 之前文章有介绍在ASP.NET WebAPI 中使用Swagger生成文档,ASP.NET Core 1. ...

  9. 用ASP.NET Core 1.0中实现邮件发送功能

    准备将一些项目迁移到 asp.net core 先从封装类库入手,在遇到邮件发送类时发现在 asp.net core 1.0中并示提供SMTP相关类库,于是网上一搜发现了MailKit 好东西一定要试 ...

随机推荐

  1. 还有更惨的吗?字节面经,美团,网易,招银,360全部在HR前一面挂了

    最近一朋友向我吐槽去年的秋招,字节面经,美团,网易,招银,360全部在HR前一面挂了,实在是有点惨.我把他语无伦次的话做了一个整理: 最近真的很暴躁,控制不住自己陷入情绪低落胡思乱想,每天都是在希望失 ...

  2. 去除Windows-XP系统下的关机功能

    by han 去除Windows-XP系统下的关机功能 一.去除"开始菜单"中"关机"功能 1.点击左下角"开始",然后"运行&q ...

  3. 复杂字符串转List<JSONObject>

    public static List<JSONObject> getlist(String data){ ObjectMapper mapper = new ObjectMapper(); ...

  4. Vue-axios 封装了一手好axios:)

    请求方式 很多种请求方式,重点还是第一种吧 下载 npm install axios --save 下载完成 直接导入 import axios from 'axios' 简单配置 axios({ u ...

  5. Cookie和Session得使用理解

    Cookie 饼干 什么是Cokkie? 1.Cookie 翻译过来是饼干的意思. 2.Cookie 是服务器通知客户端保存键值对的一种技术. 3.客户端有了 Cookie 后,每次请求都发送给服务器 ...

  6. Linux 中的虚拟网络接口

    独立博客地址:https://ryan4yin.space/posts/linux-virtual-network-interfaces/ 本文用到的字符画工具:vscode-asciiflow2 L ...

  7. system V信号量和Posix信号量

    一.函数上的区别 信号量有两种实现:传统的System V信号量和新的POSIX信号量.它们所提供的函数很容易被区分:对于所有System V信号量函数,在它们的名字里面没有下划线.例如,应该是sem ...

  8. MySQL 不完全入门指南

    由于 MySQL 的整个体系太过于庞大,文章的篇幅有限,不能够完全的覆盖所有的方面.所以我会尽可能的从更加贴进我们日常使用的方式来进行解释. 小白眼中的 MySQL 首先,对于我们来说,MySQL 是 ...

  9. Let's Encrypt泛域名使用和Nginx配置拆分

    上一期写了 使用Let's Encrypt实现网站https化 ,随着二级域名的增多,每个二级域名需要一张 SSL 证书,这可太不优雅了,泛域名表示我可以更优雅. 作者:IT王小二 博客:https: ...

  10. 用三个while循环和tkinter实现一个显示屏

    用三个while循环和tkinter实现一个显示屏 import tkinter as tk import time # 输入框是跟程序打交道的一个途径,例如程序要求你输入账号密码,那么它就需要提供两 ...