优化模块资源释放与异步预加载

This commit is contained in:
2026-09-03 14:16:48 +08:00
parent a263522b47
commit fddab16bd2
35 changed files with 1553 additions and 977 deletions
+239 -4
View File
@@ -52,6 +52,170 @@ namespace Lskj.Main.Model
DeferredForWpf
}
/// <summary>
/// WinForms-owned module lifetime token. Resources are registered while
/// the module is opened and released from this scope on close.
/// </summary>
public sealed class ModuleCloseSnapshot
{
internal readonly string ModuleId;
internal readonly XtraTabPage Page;
internal readonly Form Form;
internal readonly StaticControl.ModuleResourceScope Scope;
internal ModuleCloseSnapshot(
string moduleId,
XtraTabPage page,
Form form,
StaticControl.ModuleResourceScope scope)
{
ModuleId = moduleId ?? string.Empty;
Page = page;
Form = form;
Scope = scope;
}
}
/// <summary>
/// Prepares a close token without inspecting the hosted control tree.
/// The module scope has already registered its resources during open;
/// the fallback registration only covers legacy callers that create a
/// page outside AddModuleToTab.
/// </summary>
public static ModuleCloseSnapshot PrepareModuleClose(
DllModule module,
XtraTabPage page)
{
Form form = module == null ? null : module.ModuleForm as Form;
string moduleId = module == null
? Convert.ToString(page == null ? null : page.Tag)
: Convert.ToString(module.Tag);
// Legacy callers historically set only XtraTabPage.Tag. Prefer the
// page key when the module object has not been assigned its GUID so
// each close operation resolves its own scope and dictionary entry.
if (string.IsNullOrWhiteSpace(moduleId))
moduleId = Convert.ToString(page == null ? null : page.Tag);
moduleId = moduleId ?? string.Empty;
if (module != null && !string.IsNullOrWhiteSpace(moduleId))
module.Tag = moduleId;
StaticControl.ModuleResourceScope scope;
bool created = false;
lock (ModuleScopesSync)
{
if (!ModuleScopes.TryGetValue(moduleId, out scope) || scope == null)
{
scope = StaticControl.FindAttachedModuleScope(form) ??
StaticControl.FindAttachedModuleScope(page);
if (scope == null)
{
scope = new StaticControl.ModuleResourceScope(moduleId);
created = true;
}
ModuleScopes[moduleId] = scope;
}
}
if (created)
{
StaticControl.AttachModuleScope(form, scope);
StaticControl.AttachModuleScope(page, scope);
RegisterOwnedModuleObjects(scope, form, page);
}
return new ModuleCloseSnapshot(moduleId, page, form, scope);
}
/// <summary>
/// Completes a previously prepared module close by disposing its
/// registration scope. No control-tree traversal is performed here.
/// </summary>
public static void CompleteModuleClose(ModuleCloseSnapshot snapshot)
{
if (snapshot == null)
return;
try
{
if (snapshot.Scope != null)
snapshot.Scope.Dispose();
}
catch (Exception exception)
{
LogHelper.Instance.WriteError(exception);
}
lock (ModuleScopesSync)
{
StaticControl.ModuleResourceScope current;
if (ModuleScopes.TryGetValue(snapshot.ModuleId ?? string.Empty, out current) &&
ReferenceEquals(current, snapshot.Scope))
{
ModuleScopes.Remove(snapshot.ModuleId ?? string.Empty);
}
}
}
public static void CompleteAllModuleCloses()
{
List<StaticControl.ModuleResourceScope> scopes;
lock (ModuleScopesSync)
{
scopes = ModuleScopes.Values.Where(scope => scope != null).Distinct().ToList();
ModuleScopes.Clear();
}
foreach (StaticControl.ModuleResourceScope scope in scopes)
{
try
{
scope.Dispose();
}
catch (Exception exception)
{
LogHelper.Instance.WriteError(exception);
}
}
// Legacy page-open paths create scopes directly and therefore do
// not enter ModuleScopes. Release those scopes during the global
// shutdown pass as well.
StaticControl.CompleteAllModuleResourceScopes();
}
private static void RegisterOwnedModuleObjects(
StaticControl.ModuleResourceScope scope,
Form form,
XtraTabPage page)
{
if (scope == null)
return;
if (form != null)
{
scope.Register(form, () =>
{
if (!form.IsDisposed)
form.Dispose();
});
}
if (page != null)
{
scope.Register(page, () =>
{
if (!page.IsDisposed)
page.Dispose();
});
}
}
/// <summary>
/// Finds the search control registered by the module during creation.
/// The lookup is direct and does not inspect the WinForms control tree.
/// </summary>
public static MyControl FindModuleSearchControl(string moduleId)
{
if (string.IsNullOrWhiteSpace(moduleId))
return null;
return StaticControl.FindModuleSearchControl(moduleId);
}
/// <summary>
/// 主程序
/// </summary>
@@ -91,6 +255,12 @@ namespace Lskj.Main.Model
/// </summary>
public static Dictionary<string, DllModule> ModuleForms = new Dictionary<string, DllModule>();
/// <summary>
/// 每个已打开模块的资源登记作用域。键与 ModuleForms 中的页签标识一致。
/// </summary>
private static readonly Dictionary<string, StaticControl.ModuleResourceScope> ModuleScopes =
new Dictionary<string, StaticControl.ModuleResourceScope>(StringComparer.OrdinalIgnoreCase);
private static readonly object ModuleScopesSync = new object();
/// <summary>
/// 记录F4配置快速菜单模块
/// </summary>
public static Form QuickMenuForm = null;
@@ -513,7 +683,30 @@ namespace Lskj.Main.Model
return false;
if (!form.Visible)
form.Show();
{
StaticControl.ModuleResourceScope scope;
lock (ModuleScopesSync)
{
ModuleScopes.TryGetValue(moduleId, out scope);
}
if (scope != null && scope.IsDisposed)
return false;
// DeferredForWpf returns before Form.OnLoad. Re-establish the
// module scope for activation so MyControl and its Timer are
// registered even though the original open call has returned.
if (scope == null)
{
form.Show();
}
else
{
using (StaticControl.PushModuleScope(scope))
{
form.Show();
}
}
}
if (form.IsDisposed)
return false;
@@ -601,6 +794,8 @@ namespace Lskj.Main.Model
string guid = null;
XtraTabPage tp = null;
Form form = null;
StaticControl.ModuleResourceScope moduleScope = null;
IDisposable moduleScopeContext = null;
bool trackDogVerifiedModule = false;
try
{
@@ -701,10 +896,22 @@ namespace Lskj.Main.Model
}
guid = Guid.NewGuid().ToString();
moduleScope = new StaticControl.ModuleResourceScope(guid);
lock (ModuleScopesSync)
{
ModuleScopes[guid] = moduleScope;
}
moduleScopeContext = StaticControl.PushModuleScope(moduleScope);
tp = new XtraTabPage();
StaticControl.AttachModuleScope(tp, moduleScope);
moduleScope.Register(tp, () =>
{
if (!tp.IsDisposed)
tp.Dispose();
});
if (trackDogVerifiedModule)
{
StaticControl.DogVerifyModuleForms.Add(tp);
StaticControl.RegisterProtectedPage(tp);
}
ERPInfo.Instance.selectFormModleId = module.Id;
@@ -715,6 +922,12 @@ namespace Lskj.Main.Model
//iform.SubForm.WindowState = _frmMain.WindowState;
iform.SubForm.WindowState = FormWindowState.Normal; ;
form = iform.SubForm; // 表示组成应用程序的用户界面的窗口或对话框。
StaticControl.AttachModuleScope(form, moduleScope);
moduleScope.Register(form, () =>
{
if (!form.IsDisposed)
form.Dispose();
});
form.Tag = guid;
// 这个必须有不然会提示:"不能向tabControl中添加顶级控件"
form.TopLevel = isQuickLoad;
@@ -792,12 +1005,22 @@ namespace Lskj.Main.Model
}
catch (Exception ex)
{
if (moduleScopeContext != null)
{
moduleScopeContext.Dispose();
moduleScopeContext = null;
}
CleanupFailedModuleOpen(guid, tp, form);
string Message = ErrorMessage.PromptErrorMessage(ex);
MessageUtil.Show(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
LogHelper.Instance.WriteError(ex);
//LogUtil.WriteError("打开" + module.Name + "模块失败!", ex);
}
finally
{
if (moduleScopeContext != null)
moduleScopeContext.Dispose();
}
}
private static void CleanupFailedModuleOpen(
@@ -812,19 +1035,31 @@ namespace Lskj.Main.Model
ModuleForms.Remove(guid);
}
StaticControl.ModuleResourceScope moduleScope = null;
if (!string.IsNullOrWhiteSpace(guid))
{
lock (ModuleScopesSync)
{
if (ModuleScopes.TryGetValue(guid, out moduleScope))
ModuleScopes.Remove(guid);
}
if (moduleScope != null)
moduleScope.Dispose();
}
if (tabPage != null)
{
StaticControl.DogVerifyModuleForms.Remove(tabPage);
ERPInfo.Instance.RemoveModulePage(tabPage);
}
if (form != null && !form.IsDisposed)
if (moduleScope == null && form != null && !form.IsDisposed)
{
form.Close();
form.Dispose();
}
if (tabPage != null && !tabPage.IsDisposed)
if (moduleScope == null && tabPage != null && !tabPage.IsDisposed)
{
tabPage.Dispose();
}