Что не так с этим асинхронным кодом? - PullRequest
0 голосов
/ 22 мая 2018

У меня есть код ниже, который извлекает группы из Active Directory.Когда я запускаю код только для MY_GROUP_NAME (закомментировано ниже), вывод будет таким, как ожидалось.

Когда я запускаю полный набор групп из AD, окончательный набор данных неверен.Одним конкретным примером является то, что я получаю несколько групп объявлений в списке с одним и тем же именем группы, но разными ParentGroupGuids.Это неверный сценарий.Эта проблема, похоже, как-то связана с Parallel.ForEach(), вызывающим рекурсивный метод ниже.

Есть идеи, что это за проблема и как ее исправить?

private ConcurrentBag<Core.Models.ADGroup> adGroups;        

public async Task<List<Core.Models.ADGroup>> GetADGroupsFromADAsync(string domainName)
{
    return await Task.Run(async() =>
    {
        var domainId = await new DomainRepository().GetDomainId(domainName);

        using (var context = new PrincipalContext(ContextType.Domain, domainName))
        {
            var ps = new PrincipalSearcher(new GroupPrincipal(context));
            Parallel.ForEach(
                ps.FindAll().ToList(),
                //ps.FindAll().Where(x => x.Name == "MY_GROUP_NAME").ToList(),
                new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount },
                async (group, loopState) =>
                {
                    await GetGroupsRecursive((Guid)domainId, null, (GroupPrincipal)group);
                });
        }

        //return group list
        return adGroups.ToList();
    });
}

private async Task GetGroupsRecursive(Guid domainId, Guid? parentGroupGuid, GroupPrincipal group)
{
    //cast result to adgroup
    var adGroup = Mapper.Map<Core.Models.ADGroup>(group);

    //set domainid
    adGroup.DomainId = domainId;

    //set parent group id
    adGroup.ParentGroupGuid = parentGroupGuid;

    //process child groups
    foreach (var member in group.Members)
        if (member is GroupPrincipal)
            await GetGroupsRecursive(domainId, adGroup.Guid, (GroupPrincipal)member);

    //add to the list
    adGroups.Add(adGroup);
}

1 Ответ

0 голосов
/ 22 мая 2018

Не безопасно использовать PrincipalContext из нескольких потоков одновременно.Внутренне вызов group.Members, который вы имеете в GetGroupsRecursive , вызовет ContextRaw.QueryCtx.GetGroupMembership(this, false);, который использует основной контекст.

Это, вероятно, вызывает ваши ошибки.Вам нужен контекст для каждого потока или не многопоточный поиск членов.

РЕДАКТИРОВАТЬ: Еще одна серьезная проблема, с которой столкнулся ваш код (не видел ее, пока я не попытался написатьпример), вы используете async / await с вызовом Parallel.ForEach. Это не поддерживается, вы можете выполнять синхронные методы только с Parallel.ForEach, избавиться от асинхронного или переключиться на Поток данных TPL .

Вот пример исправления async / await и превращения его в контекст для потока

public async Task<List<Core.Models.ADGroup>> GetADGroupsFromADAsync(string domainName)
{
    return await Task.Run(async() =>
    {
        var domainId = await new DomainRepository().GetDomainId(domainName);

        using (var searchContext = new PrincipalContext(ContextType.Domain, domainName))
        {
            var ps = new PrincipalSearcher(new GroupPrincipal(searchContext));
            Parallel.ForEach(
                ps.FindAll().Select(x=>x.DistinguishedName),
                new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount },
                () => new PrincipalContext(ContextType.Domain, domainName),
                (distinguishedName, loopState, threadLocalContext) =>
                {
                    var threadLocalGroup = GroupPrincipal.FindByIdentity(threadLocalContext, IdentityType.DistinguishedName, distinguishedName);
                    GetGroupsRecursive((Guid)domainId, null, threadLocalGroup);
                    return threadLocalContext;
                },
                threadLocalContext => threadLocalContext.Dispose());
        }

        //return group list
        return adGroups.ToList();
    });
}

private void GetGroupsRecursive(Guid domainId, Guid? parentGroupGuid, GroupPrincipal group)
{
    //cast result to adgroup
    var adGroup = Mapper.Map<Core.Models.ADGroup>(group);

    //set domainid
    adGroup.DomainId = domainId;

    //set parent group id
    adGroup.ParentGroupGuid = parentGroupGuid;

    //process child groups
    foreach (var member in group.Members)
        if (member is GroupPrincipal)
            GetGroupsRecursive(domainId, adGroup.Guid, (GroupPrincipal)member);

    //add to the list
    adGroups.Add(adGroup);
}
...