Вот фрагмент кода, в котором я пытаюсь выполнить различные асинхронные методы, которые необходимо выполнять в определенном порядке (части 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
Это то, что я имел в виду.
ошибка возникает только 1, максимум 2 раза, и каждый раз выдает ошибку в другой строке", - в следующий раз зафиксируйте ошибку и добавьте ее в вопрос.
«проблема в том, что я не могу привести воспроизводимый пример» - будет достаточно компилируемого и работающего примера.
@GuruStron Я уловил: "Collection was modified; enumeration operation may not execute."
, так что, может быть, это потому, что если моя функция addToListDictionary
, я сделал некоторые вычисления, и между началом и концом функции целевой словарь был отредактирован конкурентной асинхронной функцией?
@ Siegfried.V Гуру Строн прав. ваш List
не является потокобезопасным. вы должны иметь в виду, что у параллелизма есть свои подводные камни. некоторые описывали async
-await
как «... одновременное выполнение нескольких задач». он не включает фактические потоки, но по мере того, как ваш код выполняет итерацию, this.ListDictionaries.FirstOrDefault(...
другие вызовы могут уже попасть в this.ListDictionaries.Add(group)
- следовательно, сообщение об ошибке.
@Guru, так что вы были правы, я все изменил следующим образом: у меня больше нет класса GroupDictionary
, я заменил private List<GroupDictionary> listDictionaries
на private ConcurrentDictionary<string, ConcurrentDictionary<int, IfcElement>> listDictionaries
, и теперь ошибка больше не появляется. Спасибо
@BagusTesa да, на самом деле, я внес изменения, как посоветовал Гуру, и это решило проблему. Я новичок в асинхронных методах, даже не знал, что такие ConcurrentDictionary
существуют, думаю, мне нужно найти хороший учебник по этому поводу, потому что, поскольку я много работаю с 3D, асинхронность очень полезна для оптимизации времени.
@GuruStron, могу я попросить вас написать ответ, пожалуйста, объяснив, о чем вы написали ConcurrentDictionary
, чтобы я мог принять и закрыть вопрос. Еще раз спасибо, это был хороший урок на сегодня
У вас есть очевидные проблемы с потокобезопасностью (т.е. вы пытаетесь выполнить какую-то операцию, которая не является потокобезопасной из нескольких потоков одновременно). Есть несколько способов решить эту проблему — с помощью блокировки или некоторых примитивов синхронизации.
Но в этом случае кажется, что основным источником проблем является работа со стандартными коллекциями из нескольких потоков, что не является потокобезопасным (поскольку потокобезопасность обычно обходится ценой производительности и не всегда необходима). Вы можете начать с переключения на соответствующие коллекции из пространства имен System.Collections.Concurrent.
Чтобы углубиться, рекомендую бесплатную электронную книгу Джозефа Альбахари Threading in C#.
Большое спасибо за ссылку. И на самом деле замена всего на ConcurrentDictionary замедлила работу (но работает)
@ Siegfried.V Siegfried.V да, вот почему коллекции «по умолчанию» не являются потокобезопасными - потокобезопасность обычно требует некоторых затрат на производительность.
Еще раз, пожалуйста, один вопрос: если я понял, основная проблема заключается в том, что я добавляю элементы в ListDictionary, что делает его небезопасным. Итак, в принципе, если я сначала создам этот ListDictionary (как я сделал это в описании вопроса), и мне не нужно создавать новые группы в функции, это может решить проблему?
@Siegfried.V из того, что я могу предположить из предоставленного кода - да, если вы можете предварительно заполнить словарь и не будете обновлять одни и те же элементы из нескольких потоков - тогда да, вы можете решить проблему. Но без полного обзора кода трудно сказать.
Я понимаю, что без кода сложно сказать, но попробую, еще раз спасибо
1) Какие ошибки? 2) Можете ли вы предоставить минимальный воспроизводимый пример ? 3) явно здесь происходят некоторые небезопасные для потоков операции, например, работа с неконкурентной коллекцией из нескольких потоков. Почти уверен, что если вы измените свои коллекции (которые мы не можем видеть) на их параллельные аналоги (см. пространство имен System.Collections.Concurrent), вы решите, по крайней мере, часть проблемы (если не полностью).