fix: 收敛旧模块资源释放与诊断

This commit is contained in:
2026-08-15 10:13:08 +08:00
parent 222ae3c08a
commit c49015d390
60 changed files with 4218 additions and 409 deletions
+6 -2
View File
@@ -13,9 +13,13 @@
/// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
if (disposing)
{
components.Dispose();
DisposeReportGridViews();
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
+576
View File
@@ -24,6 +24,8 @@ using Lskj.Business.Impl;
using Lskj.Business;
using System.Threading.Tasks;
using System.Collections;
using System.IO;
using System.Reflection;
namespace Lskj.Report
{
@@ -34,9 +36,19 @@ namespace Lskj.Report
/// </summary>
public DynamicReportModel Model;
private readonly HashSet<DevExpress.XtraGrid.Views.Base.BaseView>
mPreexistingGridLocalizerViews = CaptureGridLocalizerViews();
private readonly HashSet<DevExpress.XtraGrid.Views.Base.BaseView>
mReportGridLocalizerViews =
new HashSet<DevExpress.XtraGrid.Views.Base.BaseView>();
private bool mReportGridResourcesReleased;
private int mDisposedReportGridViewCount;
private int mRemovedGridLocalizerHandlerCount;
public FrmMain()
{
InitializeComponent();
this.Disposed += OnFrmMainDisposed;
}
/// <summary>
@@ -66,6 +78,7 @@ namespace Lskj.Report
this.mControl.Details = details;
this.mControl.InitControls(Model);
CaptureReportGridLocalizerViews();
this.FormClosing += new FormClosingEventHandler(OnClose);
}
catch (Exception ex)
@@ -86,7 +99,570 @@ namespace Lskj.Report
private void OnClose(object sender, FormClosingEventArgs e)
{
WriteReportLifecycleDiagnostics("closing-before-close-module");
this.mControl.CloseModule();
WriteReportLifecycleDiagnostics("closing-after-close-module");
}
private void OnFrmMainDisposed(object sender, EventArgs e)
{
DisposeReportGridViews();
WriteReportLifecycleDiagnostics(
"disposed-event;disposedGridViews=" +
mDisposedReportGridViewCount +
";gridLocalizerRemoved=" +
mRemovedGridLocalizerHandlerCount);
mReportGridLocalizerViews.Clear();
mPreexistingGridLocalizerViews.Clear();
this.FormClosing -= OnClose;
this.Disposed -= OnFrmMainDisposed;
DynamicReportModel model = this.Model;
this.Model = null;
if (model == null || model.DataCaches == null)
{
return;
}
try
{
model.DataCaches.Clear();
}
catch (Exception ex)
{
LogHelper.Instance.WriteError(ex, ResourceKeys.BaseReport);
}
}
private void DisposeReportGridViews()
{
if (mReportGridResourcesReleased)
{
return;
}
mReportGridResourcesReleased = true;
WriteReportLifecycleDiagnostics("dispose-before-gridviews");
CaptureReportGridLocalizerViews();
if (this.mControl != null)
{
this.mControl.ReleaseResourcesForDispose();
}
HashSet<DevExpress.XtraGrid.Views.Base.BaseView> disposedViews =
new HashSet<DevExpress.XtraGrid.Views.Base.BaseView>();
int disposeErrorCount = 0;
int detachedDataSourceCount = 0;
foreach (System.Windows.Forms.Control control in
EnumerateControls(this.mControl))
{
DevExpress.XtraGrid.GridControl gridControl =
control as DevExpress.XtraGrid.GridControl;
if (gridControl != null)
{
try
{
if (gridControl.DataSource != null)
{
gridControl.DataSource = null;
detachedDataSourceCount++;
}
}
catch (Exception ex)
{
disposeErrorCount++;
LogHelper.Instance.WriteError(ex, ResourceKeys.BaseReport);
}
DevExpress.XtraGrid.Views.Base.BaseView[] views =
gridControl.ViewCollection
.Cast<DevExpress.XtraGrid.Views.Base.BaseView>()
.ToArray();
foreach (DevExpress.XtraGrid.Views.Base.BaseView view in views)
{
if (view == null || !disposedViews.Add(view))
{
continue;
}
try
{
view.Dispose();
}
catch (Exception ex)
{
disposeErrorCount++;
LogHelper.Instance.WriteError(ex, ResourceKeys.BaseReport);
}
}
}
Type controlType = control.GetType();
if (controlType.FullName != null &&
controlType.FullName.StartsWith(
"DevExpress.XtraTreeList.",
StringComparison.Ordinal))
{
try
{
PropertyInfo dataSourceProperty =
controlType.GetProperty(
"DataSource",
BindingFlags.Instance |
BindingFlags.Public);
if (dataSourceProperty != null &&
dataSourceProperty.CanRead &&
dataSourceProperty.CanWrite &&
dataSourceProperty.GetValue(control, null) != null)
{
dataSourceProperty.SetValue(control, null, null);
detachedDataSourceCount++;
}
}
catch (Exception ex)
{
disposeErrorCount++;
LogHelper.Instance.WriteError(ex, ResourceKeys.BaseReport);
}
}
}
int trackedGridViews = mReportGridLocalizerViews.Count;
int removedGridLocalizerHandlers =
ReleaseTrackedGridLocalizerViews(disposedViews);
mDisposedReportGridViewCount = disposedViews.Count;
mRemovedGridLocalizerHandlerCount = removedGridLocalizerHandlers;
WriteReportLifecycleDiagnostics(
"dispose-after-gridviews;dispose-attempts=" +
disposedViews.Count +
";dispose-errors=" +
disposeErrorCount +
";detachedDataSources=" +
detachedDataSourceCount +
";trackedGridViews=" +
trackedGridViews +
";gridLocalizerRemoved=" +
removedGridLocalizerHandlers);
}
private void CaptureReportGridLocalizerViews()
{
HashSet<DevExpress.XtraGrid.Views.Base.BaseView> currentViews =
CaptureGridLocalizerViews();
foreach (DevExpress.XtraGrid.Views.Base.BaseView view in currentViews)
{
if (!mPreexistingGridLocalizerViews.Contains(view))
{
mReportGridLocalizerViews.Add(view);
}
}
}
private int ReleaseTrackedGridLocalizerViews(
HashSet<DevExpress.XtraGrid.Views.Base.BaseView> disposedViews)
{
foreach (DevExpress.XtraGrid.Views.Base.BaseView view in
mReportGridLocalizerViews)
{
if (view == null ||
(disposedViews != null && !disposedViews.Add(view)))
{
continue;
}
try
{
view.Dispose();
}
catch (Exception exception)
{
LogHelper.Instance.WriteError(exception, ResourceKeys.BaseReport);
}
}
return RemoveGridLocalizerHandlers(mReportGridLocalizerViews);
}
private static HashSet<DevExpress.XtraGrid.Views.Base.BaseView>
CaptureGridLocalizerViews()
{
HashSet<DevExpress.XtraGrid.Views.Base.BaseView> result =
new HashSet<DevExpress.XtraGrid.Views.Base.BaseView>();
try
{
var activeLocalizer =
DevExpress.XtraGrid.Localization.GridLocalizer.Active;
if (activeLocalizer == null)
{
return result;
}
Type currentType = activeLocalizer.GetType();
while (currentType != null && currentType != typeof(object))
{
FieldInfo[] fields = currentType.GetFields(
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.DeclaredOnly);
foreach (FieldInfo field in fields)
{
if (!typeof(Delegate).IsAssignableFrom(field.FieldType))
{
continue;
}
object owner = field.IsStatic ? null : (object)activeLocalizer;
Delegate current;
try
{
current = field.GetValue(owner) as Delegate;
}
catch
{
continue;
}
if (current == null)
{
continue;
}
foreach (Delegate handler in current.GetInvocationList())
{
if (IsGridLocalizerHandler(handler))
{
result.Add(
(DevExpress.XtraGrid.Views.Base.BaseView)
handler.Target);
}
}
}
currentType = currentType.BaseType;
}
}
catch
{
// Diagnostics cleanup must never interfere with form construction.
}
return result;
}
private static int RemoveGridLocalizerHandlers(
HashSet<DevExpress.XtraGrid.Views.Base.BaseView> targetViews)
{
if (targetViews == null || targetViews.Count == 0)
{
return 0;
}
int removedCount = 0;
try
{
var activeLocalizer =
DevExpress.XtraGrid.Localization.GridLocalizer.Active;
if (activeLocalizer == null)
{
return 0;
}
Type currentType = activeLocalizer.GetType();
while (currentType != null && currentType != typeof(object))
{
FieldInfo[] fields = currentType.GetFields(
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.DeclaredOnly);
foreach (FieldInfo field in fields)
{
if (!typeof(Delegate).IsAssignableFrom(field.FieldType))
{
continue;
}
object owner = field.IsStatic ? null : (object)activeLocalizer;
Delegate current;
try
{
current = field.GetValue(owner) as Delegate;
}
catch
{
continue;
}
if (current == null)
{
continue;
}
Delegate retained = null;
int fieldRemovedCount = 0;
foreach (Delegate handler in current.GetInvocationList())
{
DevExpress.XtraGrid.Views.Base.BaseView view =
handler.Target as DevExpress.XtraGrid.Views.Base.BaseView;
if (IsGridLocalizerHandler(handler) &&
targetViews.Contains(view))
{
fieldRemovedCount++;
continue;
}
retained = retained == null
? handler
: Delegate.Combine(retained, handler);
}
if (fieldRemovedCount == 0)
{
continue;
}
try
{
field.SetValue(owner, retained);
removedCount += fieldRemovedCount;
}
catch
{
// Some DevExpress versions expose a read-only backing field.
}
}
currentType = currentType.BaseType;
}
}
catch
{
// Diagnostics cleanup must never interfere with form disposal.
}
return removedCount;
}
private static bool IsGridLocalizerHandler(Delegate handler)
{
return handler != null &&
string.Equals(
handler.Method.Name,
"OnLocalizer_Changed",
StringComparison.Ordinal) &&
handler.Target is DevExpress.XtraGrid.Views.Base.BaseView;
}
private void WriteReportLifecycleDiagnostics(string phase)
{
if (!IsReportDiagnosticsEnabled())
{
return;
}
try
{
LogHelper.Instance.WriteLog(
"REPORT_DIAGNOSTIC phase=" +
(phase ?? string.Empty) +
" " +
GetReportGridDiagnostics(),
"ResourceDiagnostics.Report");
}
catch
{
// Diagnostics must never interfere with module close/dispose.
}
}
private string GetReportGridDiagnostics()
{
int controlCount = 0;
int gridControlCount = 0;
int viewCount = 0;
HashSet<DevExpress.XtraGrid.Views.Base.BaseView> views =
new HashSet<DevExpress.XtraGrid.Views.Base.BaseView>();
StringBuilder viewDetails = new StringBuilder();
try
{
if (this.mControl != null)
{
foreach (System.Windows.Forms.Control control in EnumerateControls(this.mControl))
{
controlCount++;
DevExpress.XtraGrid.GridControl gridControl =
control as DevExpress.XtraGrid.GridControl;
if (gridControl == null)
{
continue;
}
gridControlCount++;
foreach (DevExpress.XtraGrid.Views.Base.BaseView view in
gridControl.ViewCollection.Cast<DevExpress.XtraGrid.Views.Base.BaseView>())
{
if (view == null || !views.Add(view))
{
continue;
}
viewCount++;
if (viewDetails.Length < 1800)
{
if (viewDetails.Length > 0)
{
viewDetails.Append(",");
}
viewDetails.Append(SafeViewDescription(view));
}
}
}
}
}
catch (Exception exception)
{
viewDetails.Append(",enumeration-error=");
viewDetails.Append(exception.GetType().Name);
}
return "formDisposed=" +
this.IsDisposed +
";controlCount=" +
controlCount +
";gridControlCount=" +
gridControlCount +
";viewCount=" +
viewCount +
";views=" +
viewDetails +
";gridLocalizer=" +
DescribeGridLocalizer();
}
private static string SafeViewDescription(
DevExpress.XtraGrid.Views.Base.BaseView view)
{
try
{
return view.GetType().FullName +
":" +
(view.Name ?? string.Empty).Replace(";", ",");
}
catch
{
return "<view-description-error>";
}
}
private static string DescribeGridLocalizer()
{
try
{
var active =
DevExpress.XtraGrid.Localization.GridLocalizer.Active;
if (active == null)
{
return "active=null";
}
StringBuilder handlers = new StringBuilder();
int handlerCount = 0;
Type currentType = active.GetType();
while (currentType != null && currentType != typeof(object))
{
FieldInfo[] fields = currentType.GetFields(
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.DeclaredOnly);
foreach (FieldInfo field in fields)
{
if (!typeof(Delegate).IsAssignableFrom(field.FieldType))
{
continue;
}
object owner = field.IsStatic ? null : (object)active;
Delegate value = field.GetValue(owner) as Delegate;
if (value == null)
{
continue;
}
foreach (Delegate handler in value.GetInvocationList())
{
handlerCount++;
if (handlers.Length < 1800)
{
if (handlers.Length > 0)
{
handlers.Append(",");
}
string targetType = handler.Target == null
? "static"
: handler.Target.GetType().FullName;
handlers.Append(field.Name.Replace(";", ","));
handlers.Append("->");
handlers.Append(targetType.Replace(";", ","));
handlers.Append(".");
handlers.Append(handler.Method.Name.Replace(";", ","));
}
}
}
currentType = currentType.BaseType;
}
return "activeType=" +
active.GetType().FullName.Replace(";", ",") +
";handlerCount=" +
handlerCount +
";handlers=" +
handlers;
}
catch (Exception exception)
{
return "localizer-error=" + exception.GetType().Name;
}
}
private static bool IsReportDiagnosticsEnabled()
{
try
{
return File.Exists(
Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
"Logs",
"ResourceDiagnostics",
"resource-diagnostics.enabled"));
}
catch
{
return false;
}
}
private static IEnumerable<System.Windows.Forms.Control> EnumerateControls(
System.Windows.Forms.Control root)
{
if (root == null)
{
yield break;
}
yield return root;
foreach (System.Windows.Forms.Control child in root.Controls)
{
foreach (System.Windows.Forms.Control nested in EnumerateControls(child))
{
yield return nested;
}
}
}
/// <summary>