Выполнение одной и той же функции из нескольких асинхронных методов одновременно вызывает ошибки

Вот фрагмент кода, в котором я пытаюсь выполнить различные асинхронные методы, которые необходимо выполнять в определенном порядке (части await и Task.WhenAll()).

//Some other tasks before
Task<bool> taskIfcQuantityArea = Task.Run<bool>(() =>
{
    return this.addGroupStringToDictionary("IfcQuantityArea");
});
Task<bool> taskIfcQuantityLength = Task.Run<bool>(() =>
{
    return this.addGroupStringToDictionary("IfcQuantityLength");
});
Task<bool> taskIfcSiUnit = Task.Run<bool>(() =>
{
    return addGroupStringToDictionary("IfcSiUnit");
});
Task<bool> taskIfcPropertySingleValue = Task.Run<bool>(() =>
{
    return addGroupStringToDictionary("IfcPropertySingleValue");
});
//uses IfcPerson, IfcOrganization
Task<bool> taskIfcPersonAndOrganization = Task.Run<bool>(() =>
{
    return addGroupStringToDictionary("IfcPersonAndOrganization");
});
//uses IfcOrganization
Task<bool> taskIfcApplication = Task.Run(async () =>
{
    await taskIfcSiUnit;
    return addGroupStringToDictionary("IfcApplication");
});
//uses IfcSiUnit
Task<bool> taskIfcMeasureWithUnit = Task.Run(async () =>
{
    await taskIfcSiUnit;
    return addGroupStringToDictionary("IfcMeasureWithUnit");
});
//some other tasks after.

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

Вот функция:

private bool addGroupStringToDictionary(string typeName)
{
    //int processCount = await Task.Run<int>(() =>
    //{
    GroupedListStrings groupElt = this.listGrouppedStrings.FirstOrDefault(x => x.Type == typeName.ToUpper());
    if (groupElt != null)
    {
        List<string> listStringInGroup = groupElt.ListStrings;
        foreach (string line in listStringInGroup)
        {
            try
            {
                if (typeName== "IfcLocalPlacement($")
                {
                    typeName = "IfcLocalPlacement";
                }
                var type = Type.GetType("Ifc."+typeName);
                if (typeName == "IfcPropertySingleValue" || typeName == "IfcDirection" || typeName == "IfcSiUnit" || typeName == "IfcQuantityLength" || typeName == "IfcQuantityArea" || typeName == "IfcQuantityVolume" || typeName == "IfcQuantityWeight")
                {
                    try
                    {
                        object instance = Activator.CreateInstance(type, line);
                        this.addToListDictionary((IfcElement)instance);
                    }
                    catch
                    {

                    }
                }
                else if (typeName == "IfcOpeningElement")
                {
                    try
                    {
                        object instance = Activator.CreateInstance(type, line, this.listDictionaries, this.DictionaryBolts);
                        this.addToListDictionary((IfcElement)instance);
                    }
                    catch
                    {

                    }
                }
                else
                {
                    try
                    {
                        object instance = Activator.CreateInstance(type, line, this.listDictionaries);
                        this.addToListDictionary((IfcElement)instance);
                    }
                    catch
                    {

                    }
                }
            }
            catch
            {
                this.addError(line);
            }
        }
        this.listGrouppedStrings.Remove(groupElt);
        this.reportProgressImport();
    }
    //return 100;
    //});
    this.reportProgressImport();
    return true;
}

Улов получился в 1-2 раза больше чем на 1 млн строк. При каждом тесте ошибки приходят случайным образом. Возможно ли, что запуск функции одновременно из нескольких асинхронных методов является причиной проблемы?

Вот функция addToListDictionary:

private void addToListDictionary(IfcElement elt)
{
    if (elt.ErrorFound)
    {
        this.listReadButError.Add(elt);
        return;
    }
    string type = elt.GetType().ToString();
    if (elt is IfcRepere)
    {
        type = "Ifc.IfcRepere";
    }
    else if (elt is IfcRepereType)
    {
        type = "Ifc.IfcRepereType";
    }
    else if (elt is IfcPhysicalSimpleQuantity)
    {
        type = "Ifc.IfcPhysicalSimpleQuantity";
    }
    else if (elt is IfcProfileDef)
    {
        type = "Ifc.IfcProfileDef";
    }
    else if (elt is IfcGeometricRepresentationContext)
    {
        type = "Ifc.IfcGeometricRepresentationContext";
    }
    GroupDictionary group = this.ListDictionaries.FirstOrDefault(x => x.Name == type);
    if (group==null)
    {
        group = new GroupDictionary { Name = type };
        this.ListDictionaries.Add(group);
    }
    group.ListElements[elt.ID] = elt;

    if (elt is IfcMechanicalFastener)
    {
        IfcMechanicalFastener bolt = (IfcMechanicalFastener)elt;
        this.DictionaryBolts[bolt.Tag] = bolt;
    }
    else if (elt is IfcProject)
    {
        this.listProjects.Add((IfcProject)elt);
    }
    else if (elt is IfcElementAssembly ifcAss)
    {
        this.DictionaryIfcElementAssemblies[ifcAss.Key] = ifcAss;
    }
}

Также немного дополнительной информации о моем ListDictionaries:

private List<GroupDictionary> listDictionaries = new List<GroupDictionary>();
public List<GroupDictionary> ListDictionaries { get { return this.listDictionaries; } set { this.listDictionaries = value; } }

И класс GroupDictionary

public class GroupDictionary
{
    string name { get; set; }
    public string Name { get { return this.name; } set { this.name = value; } }
    public ConcurrentDictionary<int, IfcElement> ListElements = new ConcurrentDictionary<int, IfcElement>();
    public GroupDictionary()
    {

    }
}

Я сделал разные GroupDictionary потому что как только один из них мне не нужен, я его удаляю на свободное место. У меня есть один словарь с IfcPoint, он мне нужен для gt IfcPolyLine (строк), но когда я заканчиваю обрабатывать все объекты с помощью IfcPoint, я удаляю соответствующий GroupDictionary, чтобы освободить немного памяти.

@Гуру, проблема в том, что я не могу привести воспроизводимый пример. Поскольку я не могу точно сказать, в чем ошибка, я захожу в ловушку (когда я выполняю строку object instance = Activator.CreateInstance(type, line); (будет редактировать код с последней модификацией, которую я сделал. Проблема более 1M выполнения, ошибка возникает только 1, максимум 2 раза) , и каждый раз, когда другая строка выдает ошибку, я полагал, что если я выполняю Activor.CreateInstance 5-10 раз одновременно, это может быть проблемой? working with non-concurrent collection from multiple threads Это то, что я имел в виду.

Siegfried.V 27.11.2022 11:23

ошибка возникает только 1, максимум 2 раза, и каждый раз выдает ошибку в другой строке", - в следующий раз зафиксируйте ошибку и добавьте ее в вопрос.

Guru Stron 27.11.2022 11:28

«проблема в том, что я не могу привести воспроизводимый пример» - будет достаточно компилируемого и работающего примера.

Guru Stron 27.11.2022 11:29

@GuruStron Я уловил: "Collection was modified; enumeration operation may not execute.", так что, может быть, это потому, что если моя функция addToListDictionary, я сделал некоторые вычисления, и между началом и концом функции целевой словарь был отредактирован конкурентной асинхронной функцией?

Siegfried.V 27.11.2022 11:39

@ Siegfried.V Гуру Строн прав. ваш List не является потокобезопасным. вы должны иметь в виду, что у параллелизма есть свои подводные камни. некоторые описывали async-await как «... одновременное выполнение нескольких задач». он не включает фактические потоки, но по мере того, как ваш код выполняет итерацию, this.ListDictionaries.FirstOrDefault(... другие вызовы могут уже попасть в this.ListDictionaries.Add(group) - следовательно, сообщение об ошибке.

Bagus Tesa 27.11.2022 12:08

@Guru, так что вы были правы, я все изменил следующим образом: у меня больше нет класса GroupDictionary, я заменил private List<GroupDictionary> listDictionaries на private ConcurrentDictionary<string, ConcurrentDictionary<int, IfcElement>> listDictionaries, и теперь ошибка больше не появляется. Спасибо

Siegfried.V 27.11.2022 12:14

@BagusTesa да, на самом деле, я внес изменения, как посоветовал Гуру, и это решило проблему. Я новичок в асинхронных методах, даже не знал, что такие ConcurrentDictionary существуют, думаю, мне нужно найти хороший учебник по этому поводу, потому что, поскольку я много работаю с 3D, асинхронность очень полезна для оптимизации времени.

Siegfried.V 27.11.2022 12:17

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

Siegfried.V 27.11.2022 12:18
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
9
65
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

У вас есть очевидные проблемы с потокобезопасностью (т.е. вы пытаетесь выполнить какую-то операцию, которая не является потокобезопасной из нескольких потоков одновременно). Есть несколько способов решить эту проблему — с помощью блокировки или некоторых примитивов синхронизации.

Но в этом случае кажется, что основным источником проблем является работа со стандартными коллекциями из нескольких потоков, что не является потокобезопасным (поскольку потокобезопасность обычно обходится ценой производительности и не всегда необходима). Вы можете начать с переключения на соответствующие коллекции из пространства имен System.Collections.Concurrent.

Чтобы углубиться, рекомендую бесплатную электронную книгу Джозефа Альбахари Threading in C#.

Большое спасибо за ссылку. И на самом деле замена всего на ConcurrentDictionary замедлила работу (но работает)

Siegfried.V 27.11.2022 12:28

@ Siegfried.V Siegfried.V да, вот почему коллекции «по умолчанию» не являются потокобезопасными - потокобезопасность обычно требует некоторых затрат на производительность.

Guru Stron 27.11.2022 12:31

Еще раз, пожалуйста, один вопрос: если я понял, основная проблема заключается в том, что я добавляю элементы в ListDictionary, что делает его небезопасным. Итак, в принципе, если я сначала создам этот ListDictionary (как я сделал это в описании вопроса), и мне не нужно создавать новые группы в функции, это может решить проблему?

Siegfried.V 27.11.2022 12:37

@Siegfried.V из того, что я могу предположить из предоставленного кода - да, если вы можете предварительно заполнить словарь и не будете обновлять одни и те же элементы из нескольких потоков - тогда да, вы можете решить проблему. Но без полного обзора кода трудно сказать.

Guru Stron 27.11.2022 12:40

Я понимаю, что без кода сложно сказать, но попробую, еще раз спасибо

Siegfried.V 27.11.2022 12:51

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