我使用的插件和应用程序域也有一个长期运行服务时遇到因使用的DirectoryServices内存泄漏。 请注意,我用的system.directoryservices.accountmanagement但我的理解是,它使用相同的底层API ADSI的,因此很容易出现相同的内存泄漏。
我看了所有的CLR内存计数器和内存不被泄露那里,是所有返回无论是在强制GC或当我卸载应用程序域。 泄漏发生在其中不断增长专用字节。 我搜索了这里,并看到使用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的,我卸载应用程序域时,我用它做,但我认为因为有一个在你没有做到这一点DirectoryServices.Protocols无泄漏。 我只能这样做是因为我想用的AppDomain会解决我的问题,但因为它是不是在托管代码,但非托管代码的泄漏,也没有什么好处。
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
。 尝试调用Dispose的result
foreach循环中。
我还发现这太问题,但对答案没有达成一致意见。
太多的无奈和一些提示聚集在这里后,我想出了一个解决方案。 我还发现了一个有趣的事情有关如何使用using块用的DirectoryServices资源VS一个DataContext,如下面的代码片段指出的差异。 我也许并不需要使用一个终结,但我这样做只是反正是安全的。 我已经发现,通过做概述如下,我的记忆是整个运行而在此之前,我将不得不杀应用程序每天免费资源两次稳定。
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
}
}
}