ContainsKey Возвращает false для строки

Я в основном пытаюсь кэшировать некоторые организации в словаре с их идентификатором и именем. Постоянно получаю ошибку: Item with Same Key has already been added. Для меня это странно, поскольку первый блок if проверяет, содержит ли словарь ключ, и выходит из метода, если он есть.

Почему я получаю сообщение об ошибке, учитывая (упрощенный) код ниже?

    public async Task<IList<Stat>> GetStats(DateTime start, DateTime end, string role, long tenantId)
    {
    ...
        var tasks = new List<Task>();
        foreach (var org in query)
            tasks.Add(GetOrgName(org));

        await Task.WhenAll(tasks);

        return query;

    }

    private Dictionary<string, string> Orgs = new Dictionary<string, string>();

    private async Task GetOrgName(Organization org)
    {
        if (Orgs.ContainsKey(org.Id))
        {
            org.Name = Orgs[org.Id];
            return;
        }

        var result = await _directoryService.GetOrganization(org.Id);

        if (result != null)
            org.Name = result.DisplayName;
        else
            org.Name = org.Id;

        Orgs.Add(org.Id, org.Name);
    }

Вы работаете в многопоточной среде? Dictionary не является потокобезопасным.

Llama 17.12.2018 05:23

Возможно, несколько одновременных вызовов GetOrgName с одного и того же Organization?

Ilian 17.12.2018 05:46

Вы работаете в многопоточной среде?

Llama 17.12.2018 05:48

@Ilian: Может быть, ты что-то понял. Я добавил в пример еще код. Метод вызывается с помощью Task.WhenAll

dapperdan1985 17.12.2018 05:53

что-то делать с учетом регистра /

Gauravsa 17.12.2018 05:55
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
213
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Этот код выглядит опасным

    if (org != null)
        org.Name = org.DisplayName;
    else
        org.Name = org.Id;

Если org == null, я думаю, ваш пункт else вызовет исключение NullReference. Но ты этого не понимаешь

Проблема может быть в этой функции

    var org = await _directoryService.GetOrganization(org.Id);

Обратите внимание, что эта команда означает, что org может быть изменен, и она может не содержать исходный Id.

Например: если это никогда не возвращает нулевое значение, просто пустой объект. Вы можете получить исключение Item with Same Key has already been added.

Думаю, это можно исправить двойной проверкой

    if (!Orgs.ContainsKey(org.Id))
    {
        Orgs.Add(org.Id, org.Name);
    }

У меня была опечатка в части org != null. Должна была быть другая переменная. Моя ошибка. :) Только что внес правки.

dapperdan1985 17.12.2018 05:44

Поскольку к словарю обращаются несколько потоков, следует использовать ConcurrentDictionary вместо Dictionary. В противном случае может случиться так, что два потока попытаются добавить один и тот же ключ одновременно, что приведет к упомянутому вами исключению. Более того, Dictionary не является потокобезопасным и может быть поврежден при обращении к нескольким потокам.

Сделайте поиск по словарю и добавление к регистру без учета регистра. Вы можете сослаться на следующее:

private async Task GetOrgName(Organization org)
{
    if (Orgs.ContainsKey(org.Id.ToUpper()))
    {
        org.Name = Orgs[org.Id.ToUpper()];
        return;
    }

    var result = await _directoryService.GetOrganization(org.Id.ToUpper());

    if (result != null)
        org.Name = result.DisplayName;
    else
        org.Name = org.Id;

    Orgs.Add(org.Id.ToUpper(), org.Name);
}

Чтобы сделать словарь с учетом регистра, лучше использовать new Dictionary<A, B, StringComparer.OrdinalIgnoreCase) (или, в зависимости от варианта использования, любые другие средства сравнения строк). Почему? 1. о вызове ToUpper легко забыть, 2. Остерегайтесь Индейка Тест

Klaus Gütter 17.12.2018 06:54

Я просто хотел указать, что он должен сделать регистр нечувствительным. Есть несколько способов добиться этого. Спасибо, что предложили лучший вариант.

Brijesh Kumar Tripathi 17.12.2018 07:00

С вашим последним редактированием похоже, что это не многопоточная проблема, а просто проблема с несколькими асинхронными вызовами - вы, скорее всего, ожидаете и выполняете несколько запросов к GetOrganization для одного и того же org.Id, когда первый еще не завершен (так что словарь еще не обновлялся). Так что ConcurrentDictionary вам, скорее всего, не понадобится. Вместо этого вы можете сделать что-то вроде следующего:

private Dictionary<string, Task> Orgs = new Dictionary<string, Task>();

private Task GetOrgName(Organization org)
{
    Task nameTask;
    if (!Orgs.TryGetValue(org.Id, out nameTask)
    {
        nameTask = GetOrganization(.org);
        Orgs.Add(org.Id, nameTask);
    }

    return nameTask;
}

private async Task GetOrganization(Organization org)
{
    // Consider using .ConfigureAwait(false) here...
    var result = await _directoryService.GetOrganization(org.Id);
    if (result != null)
        org.Name = result.DisplayName;
    else
        org.Name = org.Id;
}

Обратите внимание на подпись для Dictionary.

Ответ принят как подходящий

Проблема в том, что к словарю одновременно обращаются несколько задач. Итак, пока одна задача проверяет, существует ли имя, другая его добавляет. Кроме того, поскольку словарь не является потокобезопасным, вы можете столкнуться со случайными исключениями с вашей текущей реализацией.

Если вы измените это:

var tasks = new List<Task>();
foreach (var org in query)
    tasks.Add(GetOrgName(org))

к:

foreach (var org in query)
   await GetOrgName(org);

У тебя не будет этой проблемы.

Другой вариант - использовать потокобезопасную коллекцию для кеша. Но если запрос содержит, скажем, 1000 элементов, ваш код может сделать тысячи одновременных вызовов _directoryService.GetOrganization. Что может быть проблемой?

Интересный. Я уже начинал этот путь раньше, но казалось, что foreach игнорирует ожидание. Я, должно быть, смешиваю свой js с моим C#.

dapperdan1985 17.12.2018 16:21

Другие вопросы по теме