首页 > 解决方案 > 多线程 DirectoryEntry 搜索完成缓慢

问题描述

我有大约 50,000 个 ID 需要在 AD 中查找,因此我使用 TPL 设置了 10 个线程来同时处理列表。浏览这些 ID 并获取所需信息需要 5 个多小时,如下所示。知道为什么吗?这是正常的还是我的代码放慢了速度?

我测试过,一个 ID 可能需要 3-4 秒到有时 70 或 80 秒来完成任何给定 ID 的迭代。

谢谢

maxThreads = 10;
int hdCount = UserProfileDictionary.Count;
int completedCount = 0;
foreach (var intKey in UserProfileDictionary.Keys.ToList())
{
    String ID = intKey;

    MyTasks.Add(System.Threading.Tasks.Task.Factory.StartNew(() => GetADInfo(ID, ref UserProfileDictionary, supportedOUCNs), TaskCreationOptions.LongRunning));

    completedCount++;
    Console.Write("\rCompleted " + completedCount + " out of " + hdCount);

    lock (numActiveThreadLock)
        numActiveThreads++;

    bool continuethreads = false;
    while (continuethreads == false)
    {
        if (numActiveThreads < maxThreads)
            continuethreads = true;
        else
            System.Threading.Thread.Sleep(1000);
    }
}
Task.WaitAll(MyTasks.ToArray(), -1);
MyTasks.Clear();
protected static void GetADInfo(String ID, ref Dictionary<string, UserProfile> UserProfileDictionary, List<string> supportedOUCNs)
{
    using (DirectoryEntry entry = new DirectoryEntry("LDAP://DC=A,DC=B,DC=C"))
    {
        using (DirectorySearcher mySearcher = new DirectorySearcher(entry))
        {
            mySearcher.SearchScope = SearchScope.Subtree;
            mySearcher.CacheResults = false;
            mySearcher.PropertiesToLoad.AddRange(new string[] { "cn", "displayName", "canonicalName", "userAccountControl", "distinguishedName"});
            mySearcher.Filter = ("(&(samAccountType=805306368)(sAMAccountName=" + ID + "))");

            foreach (SearchResult result in mySearcher.FindAll())
            {
                String displayname = "";
                String acctstatus = "N/A";
                String acctLocation = "N/A";

                String strUserDN = result.Properties["distinguishedName"][0].ToString();

                try
                {
                    displayname = result.Properties["displayName"][0].ToString();
                }
                catch
                {
                    displayname = "N/A";
                }

                acctLocation = result.Properties["canonicalName"][0].ToString().Replace(@"/" + result.Properties["cn"][0].ToString(), "");

                int userAccountControl = Convert.ToInt32(result.Properties["userAccountControl"][0]);
                bool disabled = ((userAccountControl & 2) > 0);
                if (disabled == true)
                    acctstatus = "Disabled";
                else
                    acctstatus = "Enabled";

                String suptUser = "NOT SUPPORTED";
                foreach (String CN in supportedOUCNs)
                {
                    if (acctLocation.ToLower().Contains(CN.ToLower()) == true)
                    {
                        suptUser = "SUPPORTED";
                        break;
                    }
                }

                Dictionary<string, string> usermemberOfDictionary = new Dictionary<string, string>();
                List<ResourceInfo> resInfoList = new List<ResourceInfo>();

                entry.Path = "LDAP://" + strUserDN;
                entry.RefreshCache(new string[] { "msds-memberOfTransitive" });
                foreach (String strDN in entry.Properties["msds-memberOfTransitive"])
                {
                    usermemberOfDictionary.Add(strDN, "GROUP");
                }

                String userOU = strUserDN;
                String[] OUArray = userOU.Split(',');
                foreach (String OU in OUArray)
                {
                    userOU = userOU.Replace(OU + ",", "");
                    if (userOU != "DC=net")
                    {
                        usermemberOfDictionary.Add(userOU, "OU");
                    }
                }

                foreach (KeyValuePair<string, string> DNEntry in usermemberOfDictionary)
                {
                    String strObject = "";
                    entry.Path = "LDAP://" + DNEntry.Key;
                    entry.RefreshCache(new string[] { "cn", "DriveMapping", "Priority" });
                    if (DNEntry.Value == "GROUP")
                        strObject = entry.Properties["cn"][0].ToString();
                    else
                        strObject = DNEntry.Key;

                    try
                    {
                        if (entry.Properties["DriveMapping"].Count > 0)
                        {
                            String resPriority = "";
                            try
                            {
                                resPriority = entry.Properties["Priority"][0].ToString();
                            }
                            catch
                            {
                                resPriority = "N/A";
                            }

                            PropertyValueCollection driveResources = entry.Properties["DriveMapping"];
                            for (int DRindex = 0; DRindex < driveResources.Count; DRindex++)
                            {
                                if (driveResources[DRindex].ToString().ToLower().Contains("<username>") == true)
                                {
                                    String[] driveResourceArray = driveResources[DRindex].ToString().Split(',');
                                    String resLetter = driveResourceArray[0] + @":\";
                                    String resServer = driveResourceArray[1];
                                    String resShare = driveResourceArray[2];

                                    resInfoList.Add(new ResourceInfo
                                    {
                                        resObject = strObject,
                                        resLetter = resLetter,
                                        resServer = resServer,
                                        resShare = resShare,
                                        resPriority = resPriority,
                                        resTargetLink = @"\\" + resServer + @"\" + resShare.ToLower().Replace("<username>", ID.ToLower()),
                                        resServerSupportStatus = "NOT SUPPORTED",
                                        resServerStatus = "OFFLINE",
                                        resShareStatus = "NOT ACTIVE"
                                    });
                                }
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e.Message);
                    }
                }

                lock(UserProfileDictionaryLock)
                {
                    UserProfile userProfile = UserProfileDictionary[ID.ToLower()];
                    userProfile.displayname = displayname;
                    userProfile.acctstatus = acctstatus;
                    userProfile.acctLocation = acctLocation;
                    userProfile.resources = resInfoList;
                    userProfile.userSupportStatus = suptUser;
                    UserProfileDictionary[ID.ToLower()] = userProfile;
                }
            }
            mySearcher.FindAll().Dispose();
        }
    }

    lock (numActiveThreadLock)
    {
        numActiveThreads--;
    }
}

标签: multithreadingdirectoryentrydirectorysearcher

解决方案


对我来说最明显的问题是:

mySearcher.FindAll().Dispose();

是的,你应该DisposeSearchResultCollection,但你必须处理你为循环创建的那个。第二次调用FindAll()只是重复搜索。然后你只是丢弃结果,仍然留下你以前的SearchResultCollection未处理。

你应该使用更像这样的东西:

using (var results = mySearcher.FindAll()) {
    foreach (SearchResult result in results) {

    }
}

做出这种改变应该会加快速度,因为它消除了每个帐户对 AD 的不必要调用。

我发现你的重用entry有点奇怪,但我想它有效:)

你有没有理由不包括msds-memberOfTransitive在内PropertiesToLoad?这将节省对 AD 的另一个调用。

mySearcher.PropertiesToLoad.AddRange(new string[] { "cn", "displayName", "canonicalName", "userAccountControl", "distinguishedName", "msds-memberOfTransitive"});
...
//These lines no longer needed
//entry.Path = "LDAP://" + strUserDN;
//entry.RefreshCache(new string[] { "msds-memberOfTransitive" });

foreach (String strDN in result.Properties["msds-memberOfTransitive"]) {
    ...
}

我看到这是 Windows Server 2012 中的一个新属性。我无权访问在 2012 上运行的域,所以我无法测试以确保它有效,所以也许它不会。但我知道它会返回其他构造属性(如canonicalName),所以它应该可以工作。

编辑: 另外 - 我不知道这是否有助于提高速度,但它会帮助简化你的代码 - 而不是使用lock(UserProfileDictionaryLock)你可以只做UserProfileDictionarya ConcurrentDictionary,它被设计为线程安全的。

编辑 2:如果值得付出努力,您实际上可以在一个查询中搜索多个帐户:

(&(samAccountType=805306368)(|(sAMAccountName=username1)(sAMAccountName=username2)(sAMAccountName=username3)))

LDAP 查询的最大长度显然非常大,但是您可以分批执行 50 甚至 100(或更多?)。

因此,您可以将用户名列表传递给您的GetADInfo方法,而不仅仅是一个。这确实可以减少您与 AD 建立的数字连接。


推荐阅读