System.Windows.Forms.Timer 内存泄漏问题剖析
System.Windows.Forms.Timer 内存泄漏
如果窗口中使用了 Timer 成员, 什么情况下造成内存泄漏?
- 使用 Form.Show() 方法时,修改了设计器的代码如下?
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
// 注释掉了这条语句。
// components.Dispose();
}
base.Dispose(disposing);
}
- 使用 Form.ShowDialog() 方法,未使用 using 指定窗口的实例 或 未显示调用 Dispose 方法造成内存泄漏。
踩坑的场景
我们有位资深工程师,设计了这么一个交互设计:
- 存在一个主窗口,主窗口中有一个按钮1。
- 点击按钮1时弹出一个 Form.ShowDialog() 对话框1。
- 话框1中使用 Tiemr 定时读取数据库,刷新 UI 页面。(Form 的对话框没使用 using 关键,未显示调用 Dispose)
- 查看完成,关闭对话框。
看似完成简单的 UI 操作,但是由于 Timer 未释放,导致了 Timer 的定时任务一直累计。
如果一直操作下去,那么 UI 会越来越卡(这位大神没有异步),Timer 执行任务时是切换到 UI 线程上下文执行的。
同时多个同步的数据库查询或更改任务一直执行,导致了数据库超负荷CPU占用高。
原理分析
Timer 的创建过程
public Timer(IContainer container)
: this()
{
if (container == null)
{
throw new ArgumentNullException("container");
}
// 这里添加到容器中,然后在窗口的 IDispose 中释放掉 Timer。
container.Add(this);
}
Timer.Enable 创建了非托管资源(罪魁祸首)
[SRCategory("CatBehavior")]
[DefaultValue(false)]
[SRDescription("TimerEnabledDescr")]
public virtual bool Enabled
{
get
{
if (timerWindow == null)
{
return enabled;
}
return timerWindow.IsTimerRunning;
}
set
{
lock (syncObj)
{
if (enabled == value)
{
return;
}
enabled = value;
if (base.DesignMode)
{
return;
}
if (value)
{
if (timerWindow == null)
{
timerWindow = new TimerNativeWindow(this);
}
// 这里为当前实例申请 GCHandle,它保护对象不被垃圾回收。 当不再需要 GCHandle 时,必须通过 Free() 将其释放。
// 罪魁祸首。
timerRoot = GCHandle.Alloc(this);
timerWindow.StartTimer(interval);
return;
}
if (timerWindow != null)
{
timerWindow.StopTimer();
}
if (timerRoot.IsAllocated)
{
timerRoot.Free();
}
}
}
}
那么既然是非托管的,在哪里调用了我们的委托呢?
winform 封装了一个 TimerNativeWindow 类型.
TimerNativeWindow(Timer timer)
{
_owner = timer;
}
// 在窗口过程里调用了 OnTick
protected override void WndProc(ref Message m)
{
if (m.Msg == 275)
{
if ((int)(long)m.WParam == _timerID)
{
//
_owner.OnTick(EventArgs.Empty);
return;
}
}
else if (m.Msg == 16)
{
StopTimer(destroyHwnd: true, m.HWnd);
return;
}
base.WndProc(ref m);
}
而 Timer 类是这么定义 Tick 的。
[SRCategory("CatBehavior")]
[SRDescription("TimerTimerDescr")]
public event EventHandler Tick
{
add
{
onTimer = (EventHandler)Delegate.Combine(onTimer, value);
}
remove
{
onTimer = (EventHandler)Delegate.Remove(onTimer, value);
}
}
protected virtual void OnTick(EventArgs e)
{
if (onTimer != null)
{
onTimer(this, e);
}
}
ShowDialog 时资源未释放的原因
调用 Form.ShowDialog() 来显示窗口,以下方法均没有释放 Timer 的非托管资源,故造成了内存泄漏。
public DialogResult ShowDialog()
{
return ShowDialog(null);
}
public DialogResult ShowDialog(IWin32Window owner)
{
if (owner == this)
{
throw new ArgumentException(SR.GetString("OwnsSelfOrOwner", "showDialog"), "owner");
}
if (base.Visible)
{
throw new InvalidOperationException(SR.GetString("ShowDialogOnVisible", "showDialog"));
}
if (!base.Enabled)
{
throw new InvalidOperationException(SR.GetString("ShowDialogOnDisabled", "showDialog"));
}
if (!TopLevel)
{
throw new InvalidOperationException(SR.GetString("ShowDialogOnNonTopLevel", "showDialog"));
}
if (Modal)
{
throw new InvalidOperationException(SR.GetString("ShowDialogOnModal", "showDialog"));
}
if (!SystemInformation.UserInteractive)
{
throw new InvalidOperationException(SR.GetString("CantShowModalOnNonInteractive"));
}
if (owner != null && ((int)UnsafeNativeMethods.GetWindowLong(new HandleRef(owner, Control.GetSafeHandle(owner)), -20) & 8) == 0 && owner is Control)
{
owner = ((Control)owner).TopLevelControlInternal;
}
CalledOnLoad = false;
CalledMakeVisible = false;
CloseReason = CloseReason.None;
IntPtr capture = UnsafeNativeMethods.GetCapture();
if (capture != IntPtr.Zero)
{
UnsafeNativeMethods.SendMessage(new HandleRef(null, capture), 31, IntPtr.Zero, IntPtr.Zero);
SafeNativeMethods.ReleaseCapture();
}
IntPtr intPtr = UnsafeNativeMethods.GetActiveWindow();
IntPtr intPtr2 = ((owner == null) ? intPtr : Control.GetSafeHandle(owner));
IntPtr zero = IntPtr.Zero;
base.Properties.SetObject(PropDialogOwner, owner);
Form ownerInternal = OwnerInternal;
if (owner is Form && owner != ownerInternal)
{
Owner = (Form)owner;
}
try
{
SetState(32, value: true);
dialogResult = DialogResult.None;
CreateControl();
if (intPtr2 != IntPtr.Zero && intPtr2 != base.Handle)
{
if (UnsafeNativeMethods.GetWindowLong(new HandleRef(owner, intPtr2), -8) == base.Handle)
{
throw new ArgumentException(SR.GetString("OwnsSelfOrOwner", "showDialog"), "owner");
}
zero = UnsafeNativeMethods.GetWindowLong(new HandleRef(this, base.Handle), -8);
UnsafeNativeMethods.SetWindowLong(new HandleRef(this, base.Handle), -8, new HandleRef(owner, intPtr2));
}
try
{
if (dialogResult == DialogResult.None)
{
// 这里进行对话框的消息循环。
Application.RunDialog(this);
}
}
finally
{
if (!UnsafeNativeMethods.IsWindow(new HandleRef(null, intPtr)))
{
intPtr = intPtr2;
}
if (UnsafeNativeMethods.IsWindow(new HandleRef(null, intPtr)) && SafeNativeMethods.IsWindowVisible(new HandleRef(null, intPtr)))
{
UnsafeNativeMethods.SetActiveWindow(new HandleRef(null, intPtr));
}
else if (UnsafeNativeMethods.IsWindow(new HandleRef(null, intPtr2)) && SafeNativeMethods.IsWindowVisible(new HandleRef(null, intPtr2)))
{
UnsafeNativeMethods.SetActiveWindow(new HandleRef(null, intPtr2));
}
SetVisibleCore(value: false);
if (base.IsHandleCreated)
{
if (OwnerInternal != null && OwnerInternal.IsMdiContainer)
{
OwnerInternal.Invalidate(invalidateChildren: true);
OwnerInternal.Update();
}
// 这里没有释放非托管资源。
DestroyHandle();
}
SetState(32, value: false);
}
}
finally
{
Owner = ownerInternal;
base.Properties.SetObject(PropDialogOwner, null);
}
return DialogResult;
}
为什么 Form.Show 就能释放呢?
// 这是编译器自动帮我们创建的 *.Designer.cs 的代码。
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
窗口关闭时会自动调用 Dispose 方法,如果我们注释掉 components.Dispose(),那么同样也会造成内存泄漏。
浙公网安备 33010602011771号