C#命令模式实现中的内存泄漏问题排查求助
在实现命令模式变体时遇到内存泄漏,无法定位泄漏点。使用带无限循环的线程检查同步队列元素数量(已知并非最优方案,仅作为简单示例)。相关代码如下:
WorkerThread类
public class WorkerThread { public Queue commandQueue = Queue.Synchronized(new Queue()); private Thread th; public WorkerThread() { th = new Thread(RunThread); Command.SetParentThread(this); th.Start(); } public void RunThread() { while(true) { while(commandQueue.Count > 0) { if (commandQueue.Peek() is Command) { ((Command)commandQueue.Dequeue()).DoAction(); } } System.Threading.Thread.Sleep(1); } } public void AddTaskToQueue(Command cmd) { commandQueue.Enqueue(cmd); cmd = null; } }
抽象Command类
public abstract class Command { private static WorkerThread wk; public static void SetWK(WorkerThread wk) { Command.wk = wk; } public abstract void DoAction(); }
具体实现CommandTask类
public class CommandTask : Command { public System.IO.StreamWriter sw; public Bitmap bitmap; public string dateAndTime; public override void DoAction() { sw.WriteLine(dateAndTime); sw.Flush(); sw.Dispose(); bitmap.Dispose(); dateAndTime = null; } }
添加任务代码
CommandTask ct = new CommandTask(); ct.sw = new System.IO.StreamWriter("file.txt", false); ct.dateAndTime = DateTime.Now.ToLongTimeString(); ct.bitmap = new System.Drawing.Bitmap(500, 500, System.Drawing.Imaging.PixelFormat.Format64bppArgb); wk.AddTaskToQueue(ct);
已在DoAction中释放StreamWriter和Bitmap资源,但仍存在内存泄漏,请问问题出在哪里?有没有遗漏的点?
1. 静态强引用导致对象无法回收
Command类中的静态字段wk持有WorkerThread实例的强引用,只要程序处于运行状态,这个静态引用就不会被GC回收,导致WorkerThread实例及其关联的线程、队列等资源永远无法被释放。如果你的场景不需要WorkerThread全局常驻,建议改用弱引用WeakReference<WorkerThread>,或在程序退出时手动清空该静态字段;若确实需要全局唯一实例,采用单例模式确保仅创建一个WorkerThread对象。
2. CommandTask内部资源引用未清空
在DoAction中,你仅调用了Dispose释放非托管资源,但未将sw和bitmap的引用置为null。虽然Dispose会释放底层非托管资源,但托管对象的引用仍会留在CommandTask实例中,延迟了GC对这些对象的回收时机,在高频率添加任务的场景下会加剧内存占用。
修复方式:在Dispose后清空引用:
public override void DoAction() { try { sw.WriteLine(dateAndTime); sw.Flush(); } finally { sw?.Dispose(); sw = null; bitmap?.Dispose(); bitmap = null; dateAndTime = null; } }
3. 未处理异常导致资源泄漏
DoAction中没有异常捕获逻辑,若sw.WriteLine或sw.Flush抛出异常,后续的Dispose调用会被跳过,导致StreamWriter和Bitmap的非托管资源无法释放,直接造成内存泄漏。上述修复代码中加入了try-finally块,确保无论是否发生异常,资源都能被正确释放。
4. 队列操作非原子性引发的潜在问题
RunThread中先调用Peek判断类型,再调用Dequeue取出元素,这两个操作并非原子性的。在多线程环境下,Peek之后、Dequeue之前,队列中的元素可能被其他线程移除,导致Dequeue抛出异常,中断当前任务的资源释放流程。
修复方式:直接Dequeue后判断类型,利用Queue.Synchronized的单方法线程安全性:
while(commandQueue.Count > 0) { var cmd = commandQueue.Dequeue() as Command; if (cmd != null) { cmd.DoAction(); } }
5. 忙等循环的优化(可选)
当前用Thread.Sleep(1)的忙等方式会持续占用CPU资源,同时频繁检查队列也可能加剧资源竞争。建议改用ManualResetEvent实现线程唤醒机制,减少不必要的循环:
public class WorkerThread { public Queue commandQueue = Queue.Synchronized(new Queue()); private Thread th; private ManualResetEvent _waitEvent = new ManualResetEvent(false); public WorkerThread() { th = new Thread(RunThread); Command.SetWK(this); // 修正原代码中的方法名不一致问题 th.IsBackground = true; // 设置为后台线程,避免阻止程序退出 th.Start(); } public void RunThread() { while(true) { while(commandQueue.Count > 0) { var cmd = commandQueue.Dequeue() as Command; if (cmd != null) { cmd.DoAction(); } } _waitEvent.WaitOne(); // 等待添加任务的信号 _waitEvent.Reset(); } } public void AddTaskToQueue(Command cmd) { commandQueue.Enqueue(cmd); _waitEvent.Set(); // 唤醒工作线程 } }
内容的提问来源于stack exchange,提问作者moepo

