Многопоточность DirectoryEntry Поиск медленный, чтобы завершить - PullRequest
0 голосов
/ 07 сентября 2018

У меня около 50 000 идентификаторов, которые мне нужно найти в AD, поэтому я настроил 10 потоков, используя TPL для одновременной обработки списка. Требуется более 5 часов, чтобы пройти через эти идентификаторы и получить необходимую информацию, как показано ниже. Есть идеи почему? Это нормально или я замедляю свой код?

Я проверил, и для выполнения итерации для любого данного идентификатора может потребоваться идентификатор от 3-4 секунд, иногда до 70 или 80 секунд.

Спасибо

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 голосов
/ 19 октября 2018

Самая вопиющая проблема для меня заключается в следующем:

mySearcher.FindAll().Dispose();

Да, вы должны Dispose из SearchResultCollection, но вы должны использовать тот, который вы создали для цикла. Вызов 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), поэтому он должен работать.

Edit: Кроме того - я не знаю, поможет ли это ускорить, но это поможет упростить ваш код - вместо использования lock(UserProfileDictionaryLock) вы можете просто сделать UserProfileDictionary a ConcurrentDictionary, который предназначен для потокобезопасный.

Редактировать 2: Если это того стоит, вы можете фактически найти несколько учетных записей в одном запросе:

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

Максимальная длина запроса LDAP, по-видимому, очень большая , но вы можете делать их партиями по 50 или даже 100 (или более?).

Таким образом, вы можете передать список имен пользователей вашему методу GetADInfo вместо одного. Это может реально сократить количество подключений к AD.

...