From b47a5e3e4940a174915fb9a894e707e21607d9df Mon Sep 17 00:00:00 2001 From: "1051486555@qq.com" <1412conan4869kid> Date: Tue, 9 Mar 2021 12:40:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=82=E7=85=A7https://docs.microsoft.com/zh?= =?UTF-8?q?-cn/dotnet/standard/assembly/unloadability=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=A4=96=E9=83=A8=E7=A8=8B=E5=BA=8F=E9=9B=86=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81=EF=BC=8C=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0dll=E7=9A=84=E9=9D=9E=E5=8D=A0=E7=94=A8=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=EF=BC=8C=E7=9B=91=E5=90=AC=E6=96=87=E4=BB=B6=E5=8F=98?= =?UTF-8?q?=E5=8C=96=EF=BC=8C=E5=AE=9E=E6=97=B6=E5=8A=A0=E8=BD=BD=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E7=A8=8B=E5=BA=8F=E9=9B=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- framework/Furion/App/App.cs | 95 ++++++++++++++++++- .../Furion/App/Internal/PluginLoadContext.cs | 31 ++++++ 2 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 framework/Furion/App/Internal/PluginLoadContext.cs diff --git a/framework/Furion/App/App.cs b/framework/Furion/App/App.cs index 2a299429d1..156458281d 100644 --- a/framework/Furion/App/App.cs +++ b/framework/Furion/App/App.cs @@ -5,7 +5,9 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyModel; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using StackExchange.Profiling; using System; using System.Collections.Concurrent; @@ -13,6 +15,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.Loader; using System.Security.Claims; @@ -196,6 +199,18 @@ namespace Furion /// internal static ConcurrentBag AppStartups; + /// + /// 保存文件夹的监听 + /// + private static readonly ConcurrentDictionary FileProviders = + new ConcurrentDictionary(); + + /// + /// 插件上下文的弱引用 + /// + private static readonly ConcurrentDictionary PLCReferences = + new ConcurrentDictionary(); + /// /// 获取应用有效程序集 /// @@ -228,11 +243,89 @@ namespace Furion { var assemblyFileName = externalAssembly.EndsWith(".dll") ? externalAssembly : $"{externalAssembly}.dll"; // 加载程序集 - scanAssemblies.Add(Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, assemblyFileName))); + //scanAssemblies.Add(Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, assemblyFileName))); + + // 参照 + // https://docs.microsoft.com/zh-cn/dotnet/standard/assembly/unloadability + scanAssemblies.Add(LoadExternalAssembly(Path.Combine(AppContext.BaseDirectory, assemblyFileName))); } } return scanAssemblies; } + + /// + /// 加载外部程序集 + /// + /// + /// + private static Assembly LoadExternalAssembly(string assemblyFilePath) + { + // 文件名 + string assemblyFileName = Path.GetFileName(assemblyFilePath); + // 所在文件夹绝对路径 + string assemblyFileDirPath = Directory.GetParent(assemblyFilePath).FullName; + + // 初始化外部dll为插件Context + PluginLoadContext loadContext = new PluginLoadContext(assemblyFilePath); + // 记录弱引用,unload用 + var plcWR = new WeakReference(loadContext); + PLCReferences.AddOrUpdate(assemblyFilePath, plcWR, (oldkey, oldvalue) => plcWR); + + // 获取程序集 + Assembly assembly = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(assemblyFilePath))); ; + + //添加文件夹监听,key为文件的绝对路径,每个文件都需要有一个监听 + var key = assemblyFilePath; + if (!FileProviders.ContainsKey(key)) + { + IFileProvider _fileProvider = new PhysicalFileProvider(assemblyFileDirPath); + FileProviders.AddOrUpdate(key, _fileProvider, (oldkey, oldvalue) => _fileProvider); + } + + IFileProvider fileProvider = FileProviders[key]; + + // 文件修改时触发 + ChangeToken.OnChange( + () => fileProvider.Watch(assemblyFileName), + () => UpdateExternalAssembly(assemblyFilePath, assembly.FullName)); + + return assembly; + } + + /// + /// 更新外部程序集 + /// + /// + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private static void UpdateExternalAssembly(string assemblyFilePath, string assemblyFullName) + { + lock (Assemblies) + { + Assemblies.ToList().RemoveAll(ass => ass.FullName == assemblyFullName); + + // 卸载老的 + if (PLCReferences.ContainsKey(assemblyFilePath)) + { + var plcWR = PLCReferences[assemblyFilePath]; + PLCReferences.Remove(assemblyFilePath, out _); + + if (plcWR.IsAlive) + { + ((PluginLoadContext)plcWR.Target).Unload(); + + // 需要做些延迟,AssemblyLoadContext机制不会立刻完成unload + for (int i = 0; plcWR.IsAlive && (i < 10); i++) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + } + } + + Assemblies.Append(LoadExternalAssembly(assemblyFilePath)); + }; + } } } \ No newline at end of file diff --git a/framework/Furion/App/Internal/PluginLoadContext.cs b/framework/Furion/App/Internal/PluginLoadContext.cs new file mode 100644 index 0000000000..b5910851fb --- /dev/null +++ b/framework/Furion/App/Internal/PluginLoadContext.cs @@ -0,0 +1,31 @@ +using System.IO; +using System.Reflection; +using System.Runtime.Loader; + +namespace Furion +{ + /// + /// 插件化组件Context + /// + internal class PluginLoadContext : AssemblyLoadContext + { + private AssemblyDependencyResolver _resolver; + + public PluginLoadContext(string pluginPath) : base(isCollectible: true) + { + _resolver = new AssemblyDependencyResolver(pluginPath); + } + + protected override Assembly Load(AssemblyName assemblyName) + { + string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); + if (assemblyPath != null) + { + using FileStream file = new FileStream(assemblyPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); + return LoadFromStream(file); + } + + return null; + } + } +} -- Gitee