У меня есть консольное приложение, которое запускает задания по расписанию. Работа делает 2 вещи:
1-) Запустите инструкцию SQL 2-) Отправьте по электронной почте результат этого заявления
Если я запускаю задание в последовательной форме, все работает так, как ожидалось, выполняется задание, потребление памяти увеличивается во время операции, а затем освобождается память, однако, если я запускаю задания параллельно, используя параллельную библиотеку задач, после завершения всех заданий, потребление памяти остается намного выше по сравнению с последовательной опцией, а дополнительные задания также продолжают увеличивать потребление памяти.
Чтобы быть более конкретным, я использовал следующие тестовые примеры:
Последовательность: (После того, как цикл завершен и GC собран явно для целей тестирования, потребление памяти составляет около 55 мегабайт)
for (int j = 0; j < 3; j++)
{
for (int i = 0; i < 15; i++)
{
var job = new BIJob(reportData);
job.Execute();
}
Thread.Sleep(10000);
}
Параллельно: (После того, как цикл завершен, и GC собран явно для целей тестирования, потребление памяти составляет около 85 мегабайт)
for (int j = 0; j < 3; j++)
{
for (int i = 0; i < 15; i++)
{
Task jobRunTask = Task.Factory.StartNew(() =>
{
var job = new BIJob(reportData);
job.Execute();
});
}
Thread.Sleep(10000);
}
Разница в потреблении памяти составляет примерно 30 мегабайт после 45 итераций, и дополнительная память не собирается в параллельной версии.
Что может быть причиной такого поведения? Любые идеи/комментарии приветствуются.
Когда вы выполняете несколько операций параллельно, вам нужно будет хранить достаточно памяти для работы над каждой из этих параллельных операций, вместо того, чтобы иметь рабочий набор только одного в памяти за раз. У вас также есть дополнительные потоки, каждый из которых будет потреблять память.
Память для этих операций не сможет быть восстановлена до тех пор, пока они не закончатся. Вы только начинаете операции в своем цикле, не дожидаясь их завершения, поэтому они не обязательно даже подходят для сбора всякий раз, когда вы проверяете их. Если вы дождались завершения всех операций, то они будут иметь право на сбор, хотя GC, конечно, может ждать до тех пор, пока он хочет их собирать.
List<Task> tasks = new List<Task>(); for (int j = 0; j < 3; j++) { for (int i = 0; i < 15; i++) { Task jobRunTask = Task.Factory.StartNew(() => { var job = new BIJob(reportData); job.Execute(); }); tasks.Add(jobRunTask); } Thread.Sleep(10000); } Task.WaitAll(tasks.ToArray());
Параллельная библиотека Task просто сохранит некоторые из потоков, которые она создает, в случае, если она понадобится им позже, потому что создание новых потоков является относительно дорогостоящей операцией (как с точки зрения памяти, так и с процессором).
Что касается утечки памяти: до тех пор, пока нет никакого стресса для ресурсов, нет причин для потока, используемого TPL для освобождения любых потоков. Если вы хотите протестировать утечку памяти, вы можете просто увеличить количество циклов. Не должно быть разницы в использовании памяти после того, как говорят, что цикл повторяется 1000 раз или 1000000 раз.
job.Execute()
может обрабатывать многопоточный сценарий?