COM互操作场景下从ConcurrentDictionary迭代SafeArray内存泄漏如何解决
问题成因
- 你存入
ConcurrentDictionary的并非原生数据,而是JScript数组对应的COM RCW(运行时可调用包装),该对象与首次执行写入的ASP Classic STA(单线程公寓)线程强绑定。 - 首次读取和写入在同一线程上下文,不需要COM跨公寓列集,因此访问速度正常。
- 其他线程读取该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对象存储问题:
- ASP侧写入前把对象转成JSON字符串再传入.NET
- 读取时拿到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
相关产品推荐
相关产品推荐

