.NET Core中插件式開發實現
- 2021 年 5 月 30 日
- 筆記
- .NET Core, AssemblyLoadContext
前言:
之前在文章- AppDomain實現【插件式】開發 中介紹了在 .NET Framework 中,通過AppDomain實現動態載入和卸載程式集的效果。
但是.NET Core 僅支援單個默認應用域,那麼在.NET Core中如何實現【插件式】開發呢?
一、.NET Core 中 AssemblyLoadContext的使用
1、AssemblyLoadContext簡介:
每個 .NET Core 應用程式均隱式使用 AssemblyLoadContext。 它是運行時的提供程式,用於定位和載入依賴項。 只要載入了依賴項,就會調用 AssemblyLoadContext 實例來定位該依賴項。
-
它提供定位、載入和快取託管程式集和其他依賴項的服務。
-
為了支援動態程式碼載入和卸載,它創建了一個獨立上下文,用於在其自己的 AssemblyLoadContext 實例中載入程式碼及其依賴項。
2、AssemblyLoadContext和AppDomain卸載差異:
使用 AssemblyLoadContext
和使用 AppDomain 進行卸載之間存在一個值得注意的差異。 對於 Appdomain,卸載為強制執行。
卸載時,會中止目標 AppDomain 中運行的所有執行緒,會銷毀目標 AppDomain 中創建的託管 COM 對象,等等。 對於 AssemblyLoadContext
,卸載是「協作式的」。
調用 AssemblyLoadContext.Unload 方法只是為了啟動卸載。以下目標達成後,卸載完成:
- 沒有執行緒將程式集中的方法載入到其調用堆棧上的
AssemblyLoadContext
中。 - 程式集中的任何類型都不會載入到
AssemblyLoadContext
,這些類型的實例本身由以下引用: AssemblyLoadContext
外部的引用,弱引用(WeakReference 或 WeakReference<T>)除外。AssemblyLoadContext
內部和外部的強垃圾回收器 (GC) 句柄(GCHandleType.Normal 或 GCHandleType.Pinned)。
二、.NET Core 插件式方式實現
1、創建可卸載的上下文PluginAssemblyLoadContext
class PluginAssemblyLoadContext : AssemblyLoadContext { private AssemblyDependencyResolver _resolver; /// <summary> /// 構造函數 /// isCollectible: true 重點,允許Unload /// </summary> /// <param name="pluginPath"></param> public PluginAssemblyLoadContext(string pluginPath) : base(isCollectible: true) { _resolver = new AssemblyDependencyResolver(pluginPath); } protected override Assembly Load(AssemblyName assemblyName) { string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); if (assemblyPath != null) { return LoadFromAssemblyPath(assemblyPath); } return null; } protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) { string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); if (libraryPath != null) { return LoadUnmanagedDllFromPath(libraryPath); } return IntPtr.Zero; } }
2、創建插件介面及實現
整體項目結構為:
a)添加項目PluginInterface,插件介面:
public interface IPlugin { string Name { get; } string Description { get; } string Execute(object inPars); }
b)添加HelloPlugin項目,實現不引用外部dll插件
public class HelloPlugin : PluginInterface.IPlugin { public string Name => "HelloPlugin"; public string Description { get => "Displays hello message."; } public string Execute(object inPars) {return ("Hello !!!" + inPars?.ToString());
}
}
c)添加JsonPlugin項目,實現引用三方dll插件
public class JsonPlugin : PluginInterface.IPlugin { public string Name => "JsonPlugin"; public string Description => "Outputs JSON value."; private struct Info { public string JsonVersion; public string JsonLocation; public string Machine; public DateTime Date; } public string Execute(object inPars) { Assembly jsonAssembly = typeof(JsonConvert).Assembly; Info info = new Info() { JsonVersion = jsonAssembly.FullName, JsonLocation = jsonAssembly.Location, Machine = Environment.MachineName, Date = DateTime.Now }; return JsonConvert.SerializeObject(info, Formatting.Indented); } }
d)添加PluginsApp項目,實現調用插件方法:
修改窗體介面布局:
添加執行方法
/// <summary> /// 將此方法標記為noinline很重要,否則JIT可能會決定將其內聯到Main方法中。 /// 這可能會阻止成功卸載插件,因為某些實例的生存期可能會延長到預期卸載插件的時間點之外。 /// </summary> /// <param name="assemblyPath"></param> /// <param name="inPars"></param> /// <param name="alcWeakRef"></param> /// <returns></returns> [MethodImpl(MethodImplOptions.NoInlining)] static string ExecuteAndUnload(string assemblyPath, object inPars, out WeakReference alcWeakRef) { string resultString = string.Empty; // 創建 PluginLoadContext對象 var alc = new PluginAssemblyLoadContext(assemblyPath); //創建一個對AssemblyLoadContext的弱引用,允許我們檢測卸載何時完成 alcWeakRef = new WeakReference(alc); // 載入程式到上下文 // 注意:路徑必須為絕對路徑. Assembly assembly = alc.LoadFromAssemblyPath(assemblyPath); //創建插件對象並調用 foreach (Type type in assembly.GetTypes()) { if (typeof(IPlugin).IsAssignableFrom(type)) { IPlugin result = Activator.CreateInstance(type) as IPlugin; if (result != null) { resultString = result.Execute(inPars); break; } } } //卸載程式集上下文 alc.Unload(); return resultString; }
三、效果驗證
1、非引用外部dll的插件執行:執行後對應dll成功卸載,程式集數量未增加。
2、引用外部包的插件:執行後對應dll未卸載,程式集數量增加。
通過監視查看對象狀態:該上下文在卸載中。暫未找到原因卸載失敗(疑問?)
四、總結:
雖然微軟文檔說.Net Core中使用AssemblyLoadContext來實現程式集的載入及卸載實現,但通過驗證在載入引用外部dll後,載入後不能正常卸載。或者使用方式還不正確。
參考://docs.microsoft.com/zh-cn/dotnet/standard/assembly/unloadability