You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 10:19:53