使用PrincipalSearcher.FindAll()时内存泄漏

我也有使用插件和appdomains的长时间运行服务,并且由于使用目录服务而导致内存泄漏。 请注意,我使用的是system.directoryservices.accountmanagement,但我理解它使用相同的底层ADSI API,因此容易出现相同的内存泄漏。

我查看了所有CLR内存计数器,并且内存没有泄漏,并且都是在强制GC或卸载appdomain时返回的。 泄漏是私有字节,不断增长。 我在这里搜索并看到了一些与使用ADSI API时内存泄漏相关的问题,但它们似乎表明只需遍历directorysearcher即可解决问题。 但正如您在下面的代码中看到的那样,我在foreach块中执行此操作,但内存仍然被泄露。 有什么建议? 这是我的方法:

public override void JustGronkIT() { using (log4net.ThreadContext.Stacks["NDC"].Push(GetMyMethodName())) { Log.Info("Inside " + GetMyMethodName() + " Method."); System.Configuration.AppSettingsReader reader = new System.Configuration.AppSettingsReader(); //PrincipalContext AD = null; using (PrincipalContext AD = new PrincipalContext(ContextType.Domain, (string)reader.GetValue("Domain", typeof(string)))) { UserPrincipal u = new UserPrincipal(AD); u.Enabled = true; //u.Surname = "ju*"; using (PrincipalSearcher ps = new PrincipalSearcher(u)) { myADUsers = new ADDataSet(); myADUsers.ADUsers.MinimumCapacity = 60000; myADUsers.ADUsers.CaseSensitive = false; foreach (UserPrincipal result in ps.FindAll()) { myADUsers.ADUsers.AddADUsersRow(result.SamAccountName, result.GivenName, result.MiddleName, result.Surname, result.EmailAddress, result.VoiceTelephoneNumber, result.UserPrincipalName, result.DistinguishedName, result.Description); } ps.Dispose(); } Log.Info("Number of users: " + myADUsers.ADUsers.Count); AD.Dispose(); u.Dispose(); }//using AD }//Using log4net }//JustGronkIT 

我对foreach循环进行了以下更改,但它更好,但私有字节仍在增长,并且永远不会被回收。

  foreach (UserPrincipal result in ps.FindAll()) { using (result) { try { myADUsers.ADUsers.AddADUsersRow(result.SamAccountName, result.GivenName, result.MiddleName, result.Surname, result.EmailAddress, result.VoiceTelephoneNumber, result.UserPrincipalName, result.DistinguishedName, result.Description); result.Dispose(); } catch { result.Dispose(); } } }//foreach 

我遇到了很大的内存泄漏,因为像我一样,我写了类似……

  foreach (GroupPrincipal result in searcher.FindAll()) { results.Add(result.Name); } 

但诀窍是FindAll本身返回一个必须处理的对象……

  using (var searchResults = searcher.FindAll()) { foreach (GroupPrincipal result in searchResults) { results.Add(result.Name); } } 

我很确定这是一个已知的错误( http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/6a09b8ff-2687-40aa-a278-e76576c458e0 )。

解决方法? 使用DirectoryServices库…

我说得太快了,只是积极主动调用Dispose()从长远来看并没有解决问题。 真正的解决方案? 停止使用directoryservices和directoryservices.accountmanagement并改为使用System.DirectoryServices.Protocols,并对我的域进行分页搜索,因为微软方面没有泄漏该组件。

根据要求,这里有一些代码来说明我提出的解决方案。 请注意,我还使用插件架构和appDomain,并在完成后卸载appdomain,但我认为在DirectoryServices.Protocols中没有泄漏,您不必这样做。 我之所以这样做是因为我认为使用appDomains会解决我的问题,但由于它不是托管代码中的泄漏,而是在非托管代码中,它没有任何好处。

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.DirectoryServices.Protocols; using System.Data.SqlClient; using System.Data; using System.Data.Linq; using System.Data.Linq.Mapping; using System.Text.RegularExpressions; using log4net; using log4net.Config; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Diagnostics; using System.IO; namespace ADImportPlugIn { public class ADImport : PlugIn { private ADDataSet myADUsers = null; LdapConnection _LDAP = null; MDBDataContext mdb = null; private Orgs myOrgs = null; public override void JustGronkIT() { string filter = "(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"; string tartgetOU = @"yourdomain.com"; string[] attrs = {"sAMAccountName","givenName","sn","initials","description","userPrincipalName","distinguishedName", "extentionAttribute6","departmentNumber","wwwHomePage","manager","extensionName", "mail","telephoneNumber"}; using (_LDAP = new LdapConnection(Properties.Settings.Default.Domain)) { myADUsers = new ADDataSet(); myADUsers.ADUsers.MinimumCapacity = 60000; myADUsers.ADUsers.CaseSensitive = false; try { SearchRequest request = new SearchRequest(tartgetOU, filter, System.DirectoryServices.Protocols.SearchScope.Subtree, attrs); PageResultRequestControl pageRequest = new PageResultRequestControl(5000); request.Controls.Add(pageRequest); SearchOptionsControl searchOptions = new SearchOptionsControl(System.DirectoryServices.Protocols.SearchOption.DomainScope); request.Controls.Add(searchOptions); while (true) { SearchResponse searchResponse = (SearchResponse)_LDAP.SendRequest(request); PageResultResponseControl pageResponse = (PageResultResponseControl)searchResponse.Controls[0]; foreach (SearchResultEntry entry in searchResponse.Entries) { string _myUserid=""; string _myUPN=""; SearchResultAttributeCollection attributes = entry.Attributes; foreach (DirectoryAttribute attribute in attributes.Values) { if (attribute.Name.Equals("sAMAccountName")) { _myUserid = (string)attribute[0] ?? ""; _myUserid.Trim(); } if (attribute.Name.Equals("userPrincipalName")) { _myUPN = (string)attribute[0] ?? ""; _myUPN.Trim(); } //etc with each datum you return from AD }//foreach DirectoryAttribute //do something with all the above info, I put it into a dataset }//foreach SearchResultEntry if (pageResponse.Cookie.Length == 0)//check and see if there are more pages break; //There are no more pages pageRequest.Cookie = pageResponse.Cookie; }//while loop }//try catch{} }//using _LDAP }//JustGronkIT method }//ADImport class } //namespace 

UserPrincipal实现IDisposable 。 尝试在foreach循环中调用Dispose on result

我也发现了这个问题,但答案没有达成一致。

在经历了很多挫折和一些提示后,我想出了一个解决方案。 我还发现了一个有趣的事情,即使用带有DirectoryServices资源的使用块与DataContext的区别,如下面的代码片段所示。 我可能不需要使用Finalizer但是我这样做只是为了安全。 我发现通过执行下面概述的操作,我的内存在运行中是稳定的,而在此之前我必须每天两次杀死应用程序以释放资源。

 using System.DirectoryServices; using System.DirectoryServices.AccountManagement; namespace myPlugins { public class ADImport : Plugin { //I defined these outside my method so I can call a Finalizer before unloading the appDomain private PrincipalContext AD = null; private PrincipalSearcher ps = null; private DirectoryEntry _LDAP = null; //used to get underlying LDAP properties for a user private MDBDataContext _db = null; //used to connect to a SQL server, also uses unmanaged resources public override GronkIT() { using (AD = new PrincipalContext(ContextType.Domain,"my.domain.com")) { UserPrincipal u = new UserPrincipal(AD); u.Enabled=true; using(ps = new PrincipalSearcher(u)) { foreach(UserPrincipal result in ps.FindAll()) { using (result) { _LDAP = (DirectoryEntry)result.GetUnderlyingObject(); //do stuff with result //do stuff with _LDAP result.Dispose(); //even though I am using a using block, if I do not explicitly call Dispose, it's never disposed of _LDAP.Dispose(); //even though I am using a using block, if I do not explicitly call Dispose, it's never disposed of } } } } } public override JustGronkIT() { using(_db = new MDBDataContext("myconnectstring")) { //do stuff with SQL //Note that I am using a using block and connections to SQL are properly disposed of when the using block ends } } ~ADImport() { AD.Dispose(); //This works, does not throw an exception AD = null; ps.Dispose(); //This works, does not throw an exception ps = null; _LDAP.Dispose(); //This works, does not throw an exception _LDAP = null; _db.Dispose(); //This throws an exception saying that you can not call Dispose on an already disposed of object } } }