You need to enable JavaScript to run this app. C#插件开发中无限while循环如何避免内存泄漏
优惠活动
大模型
产品
解决方案
定价
更多

C#插件开发中无限while循环如何避免内存泄漏

插件无限循环的内存泄漏问题排查与优化

核心认知澄清

你当前代码最容易引发内存泄漏的根源不是循环本身的业务逻辑,而是没有退出机制的永久循环强引用,是否使用using、是否封装类只是优化手段,要逐层拆解问题:

潜在的内存泄漏风险点

  • 线程生命周期失控:当前while(true)没有任何终止条件,如果宿主应用卸载插件时无法主动中断这个循环线程,线程作为GC根对象会强持有整个插件程序集的所有引用,导致GC完全无法回收插件加载的所有类型、静态变量、大字典数据,这是插件类程序最常见的泄漏场景。
  • 重复无意义的对象创建:每次循环都从vaProxy.SessionState强转字典、做dynamic类型绑定,虽然这些临时字典的引用在循环迭代后就会消失,正常会被GC回收,但如果运行时动态绑定没有做缓存,会生成大量动态调用的临时缓存对象,长时间运行会持续占用托管堆内存。
  • 非托管句柄泄漏:代码里的SharedMem是访问游戏共享内存的模块,如果它内部每次取状态都重新打开内存映射文件、申请内存视图而不主动关闭句柄,会持续消耗系统非托管内存,很容易被误判成循环逻辑导致的泄漏。

关于using语句的使用说明

不要在整个无限循环外面套using,这是完全无效甚至有反作用的写法:

  • using的核心作用是在代码执行到作用域末尾时,自动调用对象的IDisposable.Dispose()释放非托管资源,而你的循环进入后就永远不会走到作用域末尾,哪怕包了using,Dispose方法永远不会被执行,没有任何实际作用。
  • 绝对不要对vaProxy使用using:这个对象是宿主应用控制的,生命周期由宿主管理,你一旦Dispose掉它,后续循环里所有对它的调用都会直接报错。
  • 循环内正确使用using的场景:如果业务逻辑里需要创建临时的实现IDisposable的对象(比如临时打开的共享内存访问器、临时文件读取流),就在当前单次循环的作用域内用using包住,保证单次操作完成立刻释放非托管资源,示例:
    // 单次循环内的非托管资源用完即放
    using (var sharedMemAccessor = SharedMem.CreateAccessor())
    {
        var isFlying = sharedMemAccessor.GetStatus("isFlying");
        // 操作完立刻释放句柄
    }
    

业务逻辑封装为类的优化效果

把循环逻辑封装成类是非常推荐的做法,能从结构上降低内存泄漏风险,具体收益:

  • 可以集中管理资源的生命周期:把循环里反复用到的对象(比如回调字典、配置读取器、共享内存访问器)在类初始化时一次性缓存,不要每轮循环都重复从SessionState取值、重复做类型转换,减少临时对象的生成量。

  • 可以方便实现可终止的循环逻辑:在类内维护取消令牌,当宿主触发插件卸载时,主动中断循环、释放所有持有的引用,从根源解决线程强引用导致的程序集无法回收的问题。

  • 方便统一排查资源释放问题:所有需要Dispose的资源都可以在类的Dispose方法里统一释放,不会散落在循环代码里漏写。

    一个基础的封装结构参考:

    public class DynamicCheckLoop : IDisposable
    {
        private readonly IVAProxy _proxy;
        private readonly CancellationTokenSource _cts = new CancellationTokenSource();
        // 缓存重复使用的对象,避免每轮循环重复取值
        private readonly Dictionary<string, CallBack> _cachedCallbacks;
        private const int SleepInterval = 60 * 1000;
    
        public DynamicCheckLoop(IVAProxy proxy)
        {
            _proxy = proxy;
            // 初始化时一次性取好回调字典的引用
            _cachedCallbacks = proxy.SessionState["CallBacks"] as Dictionary<string, CallBack>;
        }
    
        public void Start()
        {
            Task.Run(async () =>
            {
                // 循环判断取消状态,替代永真的while(true)
                while (!_cts.IsCancellationRequested)
                {
                    try
                    {
                        await Task.Delay(SleepInterval, _cts.Token);
                        RunBusinessChecks();
                    }
                    catch (OperationCanceledException)
                    {
                        // 收到取消信号,正常退出循环
                        break;
                    }
                    catch (Exception ex)
                    {
                        _proxy.WriteToLog($"Dynamic Checks\n{ex.Message}", "Red");
                        Console.Beep();
                    }
                }
            }, _cts.Token);
        }
    
        private void RunBusinessChecks()
        {
            // 把原循环里的业务逻辑迁移到这里,复用缓存的_cachedCallbacks、_proxy等对象
        }
    
        public void Dispose()
        {
            // 插件卸载时触发,彻底停止循环、释放资源
            _cts.Cancel();
            _cts.Dispose();
        }
    }
    

最终的泄漏规避清单

  • 替换无终止的while(true)为可通过取消令牌中断的循环逻辑,保证插件卸载时能正确退出循环线程。
  • 把每轮循环重复获取的字典、回调引用缓存到类成员中,减少重复类型转换、动态绑定带来的临时对象开销。
  • 排查共享内存模块的句柄释放逻辑,确认非托管资源没有逐次循环累积泄漏。
  • 不要在循环内持有大对象的静态引用,保证循环迭代过程中产生的临时对象能被GC正常回收。

内容的提问来源于stack exchange,提问作者dandan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.01 10:24:02