C# LDAP开发中SearchResultAttributeCollection内存问题如何解决?
问题根因
SearchResultAttributeCollection 内部包装了WLDAP32.dll提供的非托管LDAP句柄,属于需要显式释放的资源。如果不显式释放,这些非托管资源只会在GC运行终结器队列时才会被回收,高频率大批量操作的场景下,终结器处理速度跟不上资源创建速度,就会出现内存持续上涨的问题。另外你当前代码中将所有LdapItemOperator实例存入全局List,会一直持有对象引用,导致GC即使运行也无法回收这些对象。
可行解决方案
方案1(最优):不持有SearchResultAttributeCollection引用
直接在初始化LdapItemOperator时,把所有需要用到的属性值读取出来存为普通托管类型字段,不要长期持有SearchResultAttributeCollection或者SearchResultEntry对象,从根源避免持有非托管资源:
public class LdapItemOperator { // 仅存储实际需要的业务字段,不持有LDAP原始资源引用 public string UserName { get; } public string Email { get; } // 其他需要的业务字段 public LdapItemOperator(SearchResultEntry entry) { // 构造阶段直接读取所有需要的属性值 UserName = entry.Attributes["userName"]?[0]?.ToString(); Email = entry.Attributes["email"]?[0]?.ToString(); // 后续entry和Attributes会被自动回收,不会被当前类长期持有 } }
方案2:让LdapItemOperator实现IDisposable接口
如果你确实需要在业务逻辑中随时访问LDAP原始属性,就让你的类实现IDisposable,显式释放持有的SearchResultAttributeCollection资源:
public class LdapItemOperator : IDisposable { private bool _disposed = false; private readonly SearchResultAttributeCollection LdapAttributes; public LdapItemOperator(SearchResultEntry entry) { LdapAttributes = entry.Attributes; } // 你的原有业务方法 public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { (LdapAttributes as IDisposable)?.Dispose(); } _disposed = true; } ~LdapItemOperator() { Dispose(false); } }
方案3:优化业务代码的对象生命周期管理
- 不需要保留所有
LdapItemOperator实例的场景:修改GetList方法为流式返回IEnumerable<LdapItemOperator>,处理完单个对象就立即释放,不要把所有对象存入List:
// 修改GetList为流式返回,不攒全量对象在内存 IEnumerable<LdapItemOperator> GetItemStream(string ldapFilter) { using var searchResponse = ldapConnection.SendRequest(searchRequest) as SearchResponse; foreach (SearchResultEntry entry in searchResponse.Entries) { using var item = new LdapItemOperator(entry); yield return item; } } // 业务逻辑调整为流式处理 List<bool> resultList = new List<bool>(); foreach (var item in GetItemStream(ldapFilter)) { resultList.Add(doStuff(item)); // 离开迭代作用域后using会自动释放item的资源 } Logic.WriteResultToLog(resultList);
- 必须保留所有实例的场景:处理完全部逻辑后,遍历所有实例调用
Dispose方法,再清空列表解除引用:
List<LdapItemOperator> itemList = GetList(ldapFilter); List<bool> resultList = new List<bool>(); foreach (var item in itemList) { resultList.Add(doStuff(item)); } // 处理完统一释放所有资源 foreach (var item in itemList) { item.Dispose(); } // 解除列表对对象的引用,方便GC回收 itemList.Clear(); itemList = null; Logic.WriteResultToLog(resultList);
额外注意点
- 尽量不要主动调用
GC.Collect(),只有在极端内存压力下作为临时方案使用,长期依赖主动GC会影响程序性能。 - LDAP查询时可以按需指定返回的属性列表,不要返回不需要的属性,也能降低单条
SearchResultAttributeCollection的内存占用。
内容的提问来源于stack exchange,提问作者Thom Ernst
相关产品推荐
相关产品推荐

