#c# #.net-core #memory-leaks #entity-framework-core #autofac
#c# #.net-ядро #утечки памяти #entity-framework-core #autofac
Вопрос:
Я создал небольшое консольное приложение, в котором я имитирую проблему с утечкой памяти, возникающую при использовании EF Core в приложении обмена сообщениями служебной шины Azure.
Настройка заключается в следующем:
Я наследую от DbContext в MyContext
public class MyContext : DbContext
{
private readonly Assembly configurationAssembly;
public MyContext(Assembly configurationAssembly, DbContextOptions dbContextOptions) : base(dbContextOptions)
{
this.configurationAssembly = configurationAssembly;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(this.configurationAssembly);
}
}
У меня есть класс POCO:
public class Job
{
public int Id { get; set; }
public string Applicationcode { get; set; }
}
И конфигурация EntityTypeConfiguration:
public class JobConfig : IEntityTypeConfiguration<Job>
{
public void Configure(EntityTypeBuilder<Job> builder)
{
builder.ToTable("Job_OP");
}
}
MyContext использует эту конфигурацию в переопределении OnModelCreating (ApplyConfigurationFromAssembly)
Наконец, в программе у меня есть следующий код:
class Program
{
private static IContainer container;
static void Main(string[] args)
{
var builder = new ContainerBuilder(); //Autofac dependency injection
var dbContextOptionsBuilder = new DbContextOptionsBuilder().UseSqlServer("Data Source=localhost;Initial Catalog=TestEFThreads;Integrated Security=True");
builder.Register(ctx => new MyContext(
Assembly.Load("TestEFThreads"),
dbContextOptionsBuilder.Options));
//I tried to use different lifetime scopes here, but EF doesn't like using the same dbContext concurrently in different threads.
//Currently each resolve instantiates a new MyContext
container = builder.Build();
var input = ShowMenu();
var nrOfSimulatedMessages = 0;
while (!string.Equals(input, "q", StringComparison.OrdinalIgnoreCase))
{
try
{
nrOfSimulatedMessages = Convert.ToInt32(input);
PerformSimulation(maxThreads: 10, nrOfSimulatedMessages);
}
catch (Exception e)
{
Console.WriteLine($"Incorrect input or exception, {e}");
}
input = ShowMenu().ToLower();
}
}
static string ShowMenu()
{
Console.WriteLine("Enter number of simulated messages or q to quit");
return Console.ReadLine();
}
private static void PerformSimulation(int maxThreads, int nrOfSimulatedMessages)
{
var messageCount = 0;
var tasks = new Task[maxThreads];
while (messageCount < nrOfSimulatedMessages)
{
for (int i = 0; i < maxThreads amp;amp; messageCount < nrOfSimulatedMessages; i )
{
Console.WriteLine($"Creating thread {i} for message {messageCount}");
tasks[i] = new Task(StartProcessing);
tasks[i].Start();
messageCount ;
}
Console.WriteLine("Waiting for all threads to finish");
Task.WaitAll(tasks.Where(a=>a !=null).ToArray());
}
}
private static void StartProcessing()
{
using (var lifeTime = container.BeginLifetimeScope()) //does not help
{
var myContext = container.Resolve<MyContext>();
var job = myContext.Set<Job>().SingleOrDefault(a => a.Id == 1);
Console.WriteLine($"Found job with applicationCode {job.Applicationcode}");
myContext.Dispose(); //memory leak even with dispose
}
}
}
Итак, это приложение выполняет простое чтение из базы данных и делает это указанное количество раз, каждый в отдельном потоке.
Когда я запускаю его примерно для 50000 «сообщений», объем памяти продолжает увеличиваться, как видно из диагностики. Похоже, что это не сбор мусора.
У кого-нибудь есть идея о том, как исправить эту утечку памяти?
Обновление: похоже, это связано с внедрением зависимости Autofac. Если я просто использую новый MyContext iso, разрешающий это, использование памяти в порядке.
private static void StartProcessing()
{
var myContext = new MyContext(Assembly.Load("TestEFThreads"), dbContextOptionsBuilder.Options);
var job = myContext.Set<Job>().SingleOrDefault(a => a.Id == 1);
Console.WriteLine($"Found job with applicationCode {job.Applicationcode}");
}
Однако мне нужно использовать внедрение зависимостей.
Комментарии:
1. Это не обязательно утечка памяти. Сбор мусора может подождать некоторое время, чтобы освободить память, если нет проблем с памятью со стороны других программ или системных процессов.
2. Привет, приложение в конечном итоге использует почти 4 ГБ памяти, поэтому я думаю, что это проблема. Также желтые маркеры в диагностике указывают на начало сборки мусора управляемой кучи, поэтому я бы ожидал, что он упадет туда.
3. Используете ли вы
using
инструкции? Я не вижу никакихusing
инструкций для ваших объектов DbContext. Единственноеusing
утверждение, которое я вижу, относится к этомуBeginLifetimeScope()
вызову, и это связано с autofac.DbContext
объекты имеют свое собственное управление временем жизни, и если вы не планируете найти способ изменить этот DbContext для какой-либо другой технологии, внедрение его кажется бессмысленным.4. Кроме того, вы уверены, что возвращаемый объект job не равен null ? По замыслу autofac хранит ссылки на одноразовые объекты и при вызове консоли. В вашем коде произошел сбой Writeline, DbContext никогда не будет удален. Не могли бы вы попробовать еще раз с помощью инструкции using , как было предложено выше?
5. Это может дать некоторое представление: autofaccn.readthedocs.io/en/latest/lifetime/disposal.html