.NET6 之 DLL 熱插拔(AssemblyLoadContext)紀錄
let DLL hot swap. 動態卸載。AssemblyLoadContext。
引言
參考文件
進階參考文件(未認證)
關鍵原碼紀錄
Last updated
let DLL hot swap. 動態卸載。AssemblyLoadContext。
Last updated
/// <summary>
/// ExecuteAndUnload
/// ref→[How to use and debug assembly unloadability in .NET](https://learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability)
/// </summary>
sealed class CollectibleAssemblyLoadContext : AssemblyLoadContext
{
public CollectibleAssemblyLoadContext(string name) : base(name, isCollectible: true)
{
//※ 記得設定 isCollectible = true ,才能動態卸載。
}
protected override Assembly? Load(AssemblyName name)
{
return null;
}
}[MethodImpl(MethodImplOptions.NoInlining)]
async Task<(JobResult, WeakReference)> ExecuteAndUnload2Async(TaskQueue runTask, JobMaster job, JobDetail jobStep, CancellationToken cancelToken)
{
//# 為每個 job (或插件)建立獨立的 "Domain"。
var alc = new CollectibleAssemblyLoadContext($"YourJobService_{job.JobId}");
WeakReference alcWeakRef = new WeakReference(alc, trackResurrection: true);
//-------------------------------------------------------------------------
try
{
//## 動態載入模組
//Assembly jobAsm = Assembly.LoadFrom(jobStep.TypeAsmPath);
Assembly jobAsm = alc.LoadFromAssemblyPath(jobStep.TypeAsmPath);
_logger.LogDebug($"動態載入模組:{jobStep.TypeAsmPath}。");
//## 執行排程工作
JobResult result = await InvokeJobStepAsync(jobAsm, job, jobStep, runTask, cancelToken);
return (result, alcWeakRef);
}
finally
{
//-------------------------------------------------------------------------
//# 啟動卸載(其實只是標記或宣示已『Unload』)
string jobDllFiles = String.Join(", ", alc.Assemblies.Select(asm => asm.FullName).Distinct());
_logger.LogInformation($"啟動卸載 => {jobDllFiles}。");
alc.Unload(); // 此指令只是標記卸載,該模組其實大概還在記憶體內。
}
}/// 組建並執行排程工作
async ValueTask BuildWorkItemAsync(TaskQueue runTask, CancellationToken cancelToken)
{
List<WeakReference> alcWeakRefList = new();
//※用 WeakReference 類別來追踨(標記)某DLL已『Unload』。
try
{
//# 依步驟順序執行
foreach (var step in jobStepList.OrderBy(c => c.StepSeq))
{
(JobResult result, WeakReference alcWeakRef) = await ExecuteAndUnload2Async(runTask, job, step, cancelToken);
alcWeakRefList.Add(alcWeakRef);
//※ 用 WeakReference 類別註記該 DLL 已『Unload』
if(result.code == JobResultCode.SUCCESS)
...略...
}
//# 執行完成...略...
}
catch (Exception ex)
{
string errMsg = ExtractExceptionMessage(ex);
_logger.LogCritical($"BuildWorkItem: EXCEPTION => {errMsg}");
}
finally
{
//# 最後的最後用 GC.Collect 真的釋放DLL。
int round = 0;
for (; alcWeakRefList.Any(alc => alc.IsAlive) && (round < 10); round++)
{
await Task.Yield();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}