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

COM互操作场景下从ConcurrentDictionary迭代SafeArray内存泄漏如何解决

问题成因

  1. 你存入ConcurrentDictionary的并非原生数据,而是JScript数组对应的COM RCW(运行时可调用包装),该对象与首次执行写入的ASP Classic STA(单线程公寓)线程强绑定。
  2. 首次读取和写入在同一线程上下文,不需要COM跨公寓列集,因此访问速度正常。
  3. 其他线程读取该RCW时,所有成员访问(包括索引、toArray方法调用)都需要经过COM跨线程调度,每次元素访问都会产生大量列集开销与临时代理对象,最终表现为耗时暴涨、内存占用急剧上升。

修复方案

核心原则是全局共享存储中绝对不能保存任何COM对象、RCW对象,所有跨线程共享的数据必须是完全脱离COM上下文的纯.NET原生类型。

方案1:固定结构数据转.NET原生对象存储

第一步:修改.NET组件代码

首先定义ComVisible的POCO类存储单条数据:

[ComVisible(true)]
[Guid("你自己生成的GUID")]
public class ArrayItem
{
    public int num { get; set; }
    public string str { get; set; }
}

修改TestClass的读写方法,写入时将COM对象转换为纯.NET类型存储,读取时转换为SAFEARRAY返回:

using System.Reflection;

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY")]
[ProgId("Test.Test")]
public class TestClass : ITestClass
{
    static ConcurrentDictionary<string, object> dataDic = new ConcurrentDictionary<string, object>(Environment.ProcessorCount * 4, 256);
    
    public bool TryAdd(string key, object value)
    {
        if (value is not IEnumerable enumerable) return false;
        List<ArrayItem> items = new List<ArrayItem>();
        foreach (var elem in enumerable)
        {
            if (elem is IDispatch disp)
            {
                // 读取JScript对象的属性转成.NET原生POCO
                var num = Convert.ToInt32(disp.GetType().InvokeMember("num", BindingFlags.GetProperty, null, disp, null));
                var str = Convert.ToString(disp.GetType().InvokeMember("str", BindingFlags.GetProperty, null, disp, null));
                items.Add(new ArrayItem { num = num, str = str });
            }
        }
        return dataDic.TryAdd(key, items);
    }

    public object TryGetValue(string key)
    {
        if (dataDic.TryGetValue(key, out var o) && o is List<ArrayItem> items)
        {
            // 返回.NET数组,自动封装为COM可直接识别的SAFEARRAY
            return items.ToArray();
        }
        return null;
    }
}

第二步:修改ASP代码

无需再调用toArray,直接读取返回的数组即可:

<script language="jscript" runat="server">
    var dic=Server.CreateObject('Test.Test');
    // 写入逻辑不变
    var objArray=[];
    for(var n=0; n<5000; n++)
    {
       objArray.push({num:n,str:'test'+n});
    }
    dic.TryAdd('objectArray',objArray);
    // 读取逻辑简化
    var result=dic.TryGetValue('objectArray');
    var nativeA=new Array(result.length);
    var t1=(new Date()).getTime();
    for(var i=0;i<result.length;i++)
    {
        nativeA[i] = {num: result[i].num, str: result[i].str};
    }
    Response.write('Conversion time:'+(new Date()).getTime()-t1+' miliseconds');
</script>

方案2:通用场景用JSON序列化(实现更简单)

如果数据结构不固定,不需要在.NET侧操作数据内容,直接用JSON序列化绕过COM对象存储问题:

  1. ASP侧写入前把对象转成JSON字符串再传入.NET
  2. 读取时拿到JSON字符串直接在ASP侧反序列化
// 写入
var objArray=[];
for(var n=0; n<5000; n++)
{
   objArray.push({num:n,str:'test'+n});
}
dic.TryAdd('objectArray', JSON.stringify(objArray));

// 读取
var jsonStr = dic.TryGetValue('objectArray');
var result = JSON.parse(jsonStr);

该方案完全避免了.NET侧处理COM对象反射,实现成本更低,性能也足够稳定。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 03:06:03