XDocument + IEnumerable вызывает исключение из памяти в System.Xml.Linq.dll
В основном у меня есть программа, которая при загрузке загружает список файлов (как FileInfo
) и для каждого файла в списке загружает XML-документ (как XDocument
).
Затем программа считывает данные из нее в класс контейнера (сохраняя как IEnumerables
), после чего XDocument
выходит за пределы области видимости.
Затем программа экспортирует данные из класса контейнера в базу данных. Однако после экспорта контейнерный класс выходит за пределы области видимости, сборщик мусора не очищает контейнерный класс, который, поскольку его сохранение как IEnumerable
, похоже, приводит к сохранению в памяти XDocument
(не уверен, что это является причиной, но диспетчер задач показывает, что память из XDocument
не освобождается).
По мере того, как программа перебирает несколько файлов, программа в конечном итоге бросает исключение из памяти. Для смягчения этого ive закончилось использование
System.GC.Collect();
чтобы заставить сборщик мусора работать после того, как контейнер выходит за рамки. это работает, но мои вопросы:
- Правильно ли это делать? (Заставляет сборщик мусора работать немного странно)
- Есть ли лучший способ убедиться, что память
XDocument
находится в распоряжении?
- Может ли быть другая причина, кроме IEnumerable, что память документа не освобождается?
Спасибо.
Изменить: Образцы кода:
-
Класс контейнера:
public IEnumerable<CustomClassOne> CustomClassOne { get; set; }
public IEnumerable<CustomClassTwo> CustomClassTwo { get; set; }
public IEnumerable<CustomClassThree> CustomClassThree { get; set; }
...
public IEnumerable<CustomClassNine> CustomClassNine { get; set; }
-
Пользовательский класс:
public long VariableOne { get; set; }
public int VariableTwo { get; set; }
public DateTime VariableThree { get; set; }
...
Во всяком случае, что основные структуры действительно. Пользовательские классы заполняются через класс контейнера из документа XML. Заполненные структуры сами используют очень мало памяти.
Класс контейнера заполняется из одного документа XML, выходит из области видимости, затем загружается следующий документ, например.
public static void ExportAll(IEnumerable<FileInfo> files)
{
foreach (FileInfo file in files)
{
ExportFile(file);
//Temporary to clear memory
System.GC.Collect();
}
}
private static void ExportFile(FileInfo file)
{
ContainerClass containerClass = Reader.ReadXMLDocument(file);
ExportContainerClass(containerClass);
//Export simply dumps the data from the container class into a database
//Container Class (and any passed container classes) goes out of scope at end of export
}
public static ContainerClass ReadXMLDocument(FileInfo fileToRead)
{
XDocument document = GetXDocument(fileToRead);
var containerClass = new ContainerClass();
//ForEach customClass in containerClass
//Read all data for customClass from XDocument
return containerClass;
}
Забыл упомянуть этот бит (не уверен, насколько он уместен), файлы могут быть сжаты как .gz, поэтому у меня есть метод GetXDocument()
для его загрузки
private static XDocument GetXDocument(FileInfo fileToRead)
{
XDocument document;
using (FileStream fileStream = new FileStream(fileToRead.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
if (String.Equals(fileToRead.Extension, ".gz", StringComparison.OrdinalIgnoreCase))
{
using (GZipStream zipStream = new GZipStream(fileStream, CompressionMode.Decompress))
{
document = XDocument.Load(zipStream);
}
}
else
{
document = XDocument.Load(fileStream);
}
return document;
}
}
Надеюсь, этого достаточно.
Благодаря
Изменить: System.GC.Collect()
работает не 100% времени, иногда программа сохраняет XDocument
, кто-нибудь знает, почему это может быть?
public static ContainerClass ReadXMLDocument(FileInfo fileToRead)
{
XDocument document = GetXDocument(fileToRead);
var containerClass = new ContainerClass();
//ForEach customClass in containerClass
//Read all data for customClass from XDocument
containerClass.CustomClassOne = document.Descendants(ElementName)
.DescendantsAndSelf(ElementChildName)
.Select(a => ExtractDetails(a));
return containerClass;
}
private static CustomClassOne ExtractDetails(XElement itemElement)
{
var customClassOne = new CustomClassOne();
customClassOne.VariableOne = Int64.Parse(itemElement.Attribute("id").Value.Substring(4));
customClassOne.VariableTwo = int.Parse(itemElement.Element(osgb + "version").Value);
customClassOne.VariableThree = DateTime.ParseExact(itemElement.Element(osgb + "versionDate").Value,
"yyyy-MM-dd", CultureInfo.InvariantCulture);
return customClassOne;
}
Ответы
Ответ 1
Ваш код не выглядит плохой для меня, и я не вижу никакой причины для принудительного сбора. Если ваш собственный класс содержит ссылку на XElements из XDocument, тогда GC не будет собирать ни их, ни сам документ. Если что-то еще держит ссылки на ваши перечисляемые, то они также не будут собраны. Поэтому мне бы очень хотелось увидеть ваше собственное определение класса и его заполнение.
Ответ 2
Принуждение ручной сборки мусора, возможно, решило вашу проблему в некоторых случаях, но это довольно уверенная ставка, что это не лучше, чем совпадение.
Что вам нужно сделать, так это прекратить догадываться о том, что вызывает проблемы с давлением в памяти, и вместо этого узнать наверняка.
Я использовал JetBrains dotTrace для очень хорошего эффекта в подобных ситуациях - установить точку останова, запустить профайлер и просмотреть просмотр всех "живые" объекты и их отношения. Легко найти, какие объекты по-прежнему сохраняются, и по каким ссылкам они хранятся в реальном времени.
В то время как я сам не использовал его, многие рекомендуют RedGate Ants Memory Profiler.
Оба этих инструмента имеют бесплатные испытания, которых должно быть достаточно, чтобы решить вашу текущую проблему. Хотя, я бы сказал, что стоит покупать тот или другой - dotTrace спас мне десятки часов от проблем с памятью, очень полезную рентабельность.
Ответ 3
Ваша склонность к вызову GC.Collect
верна. Необходимость вызова этого метода является признаком того, что с вашим кодом что-то не так.
Однако в ваших заявлениях есть несколько вещей, которые заставляют меня думать, что ваше понимание памяти немного не работает. Диспетчер задач - очень плохой показатель того, сколько памяти используется вашей программой; профилировщик намного лучше справляется с этой задачей. Что касается памяти, если ее можно собрать, GC будет собирать память, когда это необходимо.
Хотя это немного детализация, вы спрашиваете, как "убедиться, что память XDocument находится в распоряжении". Disposed обычно используется для ссылки на ручное освобождение неуправляемых ресурсов, таких как соединения с базой данных или файловые дескрипторы; GC собирает память.
Теперь попробуйте ответить на реальный вопрос. Очень легко иметь ссылки на объекты, которые вы не выпускаете, особенно при использовании lambdas и LINQ. Вещи, напечатанные как IEnumerable
, особенно подвержены этому, поскольку лениво оцениваемые функции LINQ почти всегда будут вводить ссылки на объекты, которые, по вашему мнению, в противном случае не используются. Код ReadXMLDocument
, который вы пропустили, может стать хорошим местом для поиска.
Еще одна возможность - это то, что TomTom предлагает тем, что используемые вами классы базы данных могут хранить объекты, которые вы не ожидали по своим собственным причинам.
Ответ 4
Если обработанные файлы XML слишком велики (около 500-800 М), вы не можете использовать XDocument (или XmlDocument), потому что он попытается загрузить весь документ в память. См. Обсуждение: Ли LINQ обрабатывает большие XML файлы? Получение OutOfMemoryException
В этом случае вам лучше использовать класс XStreamingElement и создать из него ContainerClass.
Может быть, переход на 64-битный процесс поможет, но лучше всего использовать потоковое воспроизведение из конца в конец.
Ответ 5
На самом деле это не ответ, больше предложений по исследованию: если GC.Collect не помогает, это в значительной степени означает, что вы все еще держите ссылки на объекты где-то.
Посмотрите на синглеты и кеши, которые могут содержать рефренсы.
Если вы действительно получаете исключение или можете собирать дамп памяти, вы можете использовать WinDbg + Sos, чтобы найти, кто содержит refrences для объектов: поиск "утечек памяти sos windbg" для поиска деталей.
Ответ 6
В любом случае используйте
String.Equals(fileToRead.Extension, ".gz", StringComparison.OrdinalIgnoreCase)
вместо
String.Compare()
Ответ 7
Вы можете попытаться провести оценку с помощью списка:
public static ContainerClass ReadXMLDocument(FileInfo fileToRead)
{
XDocument document = GetXDocument(fileToRead);
var containerClass = new ContainerClass();
//ForEach customClass in containerClass
//Read all data for customClass from XDocument
containerClass.CustomClassOne = document.Descendants(ElementName)
.DescendantsAndSelf(ElementChildName)
.Select(a => ExtractDetails(a)).ToList();
return containerClass;
}
Ответ 8
Затем программа экспортирует данные из класс контейнера в базу данных. После экспорта контейнерный класс выходит за рамки, однако, сборщик мусора не очищается класс контейнера, который, поскольку его сохранение как IEnumerable, похоже, приводит к XDocument, находящемуся в памяти (Не уверен, что это причина, но диспетчер задач показывает память из XDocument не освобождается).
Причина в том, что LYNC хранит КАЖДЫЙ ПУНКТ ПРОЧИТАТЬ в своем собственном пуле ссылок для транзакции. Basiaclly он делает так, чтобы taht перечитывал, что он может уникально item.
Предложение:
-
Загружайте только примитивные ключи в массив. COMMIT.
-
Пройдите по списку и обрабатывайте элементы по одному, совершая после каждого.