多线程DirectoryEntry搜索完成缓慢

时间:2018-09-07 03:28:20

标签: multithreading directoryentry directorysearcher

我需要在AD中查找大约50,000个ID,因此我使用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--;
    }
}

1 个答案:

答案 0 :(得分:0)

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

mySearcher.FindAll().Dispose();

是的,您应该使用Dispose中的SearchResultCollection,但是必须处理为循环创建的代码。再次调用FindAll()只会重复搜索。然后,您只是丢弃结果,而仍然保留先前的SearchResultCollection

您应该使用类似以下的内容:

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

    }
}

进行此更改将加快处理速度,因为它消除了每个帐户不必要的广告拨出。

我发现您对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)设为ConcurrentDictionary,而不必使用UserProfileDictionary来代替(&(samAccountType=805306368)(|(sAMAccountName=username1)(sAMAccountName=username2)(sAMAccountName=username3))) 线程安全的。

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

GetADInfo

LDAP查询的最大长度显然为very big,但是您可以批量50个甚至100个(或更多?)来完成它们。

因此,您可以将用户名列表传递给public static void main(String[] args) { int[]myArray = new int[5]; for(int index =0;index<myArray.length;index++) myArray[3]=100*myArray[3]*myArray[3]+3; System.out.println("myArray[]="+myArray[3]); } 方法,而不仅仅是一个。这确实可以减少您与AD建立的连接数量。