Почему объект, созданный GetService, не разрушается? - PullRequest
0 голосов
/ 21 апреля 2020

Я пишу приложение, ориентированное на базовую структуру do tnet 3.1. Я использую внедрение зависимостей для настройки, среди прочего, контекста базы данных. В моем Program.cs у меня есть следующий код:

var host = new HostBuilder()
    .ConfigureHostConfiguration(cfgHost =>
    {
        ...
    })
    .ConfigureAppConfiguration((hostContext, configApp) =>
    {
        ....
    })
    .ConfigureServices((hostContext, services) =>
    {
        ...
        services.AddDbContext<MyHomeContext>(options =>
        {
            options.UseNpgsql(hostContext.Configuration.GetConnectionString("DbContext"));
        }, ServiceLifetime.Transient);
        ...
    })
    .ConfigureLogging((hostContext, logging) =>
    {
        ...    
    })
    .Build();

Я передаю host другому классу. В этом другом классе у меня есть, как часть более длинного метода, следующий код:

    using (var context = Host.Services.GetService(typeof(MyHomeContext)) as MyHomeContext)
    {
        StatusValues = context.Status.ToDictionary(kvp => kvp.Name, kvp => kvp.Id);
    }
    GC.Collect();
    GC.Collect();

Вызовы GC.Collect предназначены для тестирования / расследования. В MyHomeContext I для целей тестирования реализован деструктор и переопределение Dispose (). Dispose () вызывается, но деструктор никогда не вызывается. Это приводит к утечке памяти для каждого экземпляра MyHomeContext, который я создаю.

Чего мне не хватает? Что я могу сделать, чтобы убедиться, что экземпляр MyHomeContext удаляется, когда он мне больше не нужен.

Я перешел на этот агрегат по нескольким причинам:

  • I требуется только соединение с базой данных в течение короткого промежутка времени.
  • Я вставляю много данных (не в приведенном выше сокращенном примере / тестовом коде), в результате чего DbContext сохраняет большой кэш. Я ожидал, что удаление объекта освободит память, но теперь я только усугубил это: (

Когда я заменяю Host.Services.GetService(typeof(MyHomeContext)) as MyHomeContext на new MyHomeContext(), вызывается деструктор MyHomeContext. Кажется, мне, что что-то в структуре внедрения зависимостей содержит ссылку на объект. Это правда? Если так, как я могу сказать ему , чтобы освободить это?

1 Ответ

2 голосов
/ 21 апреля 2020

Очень сложно дать хороший ответ на ваш вопрос, потому что существует довольно много заблуждений, которые необходимо устранить. Вот несколько советов, на которые следует обратить внимание:

  • Неоптимизировано (сборка отладки). NET Приложения, которые запускаются в отладчике, ведут себя совершенно иначе, чем оптимизированные приложения без отладчика. Например, при отладке все переменные метода всегда остаются ссылочными. Это означает, что любой вызов GC.Collect() не сможет очистить переменную context, на которую ссылается тот же метод.
  • Когда шаблон Dispose реализован правильно, вызов финализатора будет подавлен классом при вызове его метода Dispose. Это делается путем вызова G C .SuppressFinalize . DbContext Entity Framework правильно реализует шаблон Dispose, который также может привести к тому, что вы не увидите попадания в ваш финализатор.
  • Финализаторы (destructurs) вызываются в фоновом потоке, называемом потоком финализатора, Это означает, что даже если ваш context был разыменован и имел право на сборку мусора, финализатор вряд ли будет вызван сразу после вызова GC.Collect(). Однако вы можете остановить приложение и дождаться вызова финализаторов, вызвав G C .WaitForPendingFinalizers () . Вызов WaitForPendingFinalizers вряд ли когда-либо будет полезен в работе, но он может быть полезен для целей тестирования и тестирования.

Помимо этих частей CLR c, здесь есть некоторые отзывы о Часть DI:

  • Службы, разрешенные из контейнера DI, не должны выбрасываться напрямую. Вместо этого, поскольку DI-контейнер контролирует его создание, вы должны позволить ему также контролировать его уничтожение.
  • Способ сделать это (с помощью MS.DI) - создать IServiceScope , Службы кэшируются в такой области, и когда область удаляется, она также обеспечивает удаление своих кэшированных одноразовых служб и гарантирует, что это делается в порядке, обратном созданию.
  • Запрос служб непосредственно у Контейнер root (Host.Services в вашем случае) - плохая идея, потому что он вызывает кэширование сервисов (например, DbContext) в контейнере root. Это заставляет их эффективно становиться одиночками. Другими словами, один и тот же экземпляр DbContext будет повторно использоваться в течение всего срока действия приложения, независимо от того, как часто вы запрашиваете его у Host.Services. Это может привести к всевозможным трудностям для отладки . Решение, опять же, состоит в том, чтобы вместо этого создать область и решить из этой области. Пример:
    var factory = Host.Services.GetRequiredService<IServiceScopeFactory>();
    using (var scope = factory.CreateScope())
    {
        var service = scope.ServiceProvider.GetRequiredService<ISomeService>();
        service.DoYourMagic();
    }
    
  • Обратите внимание, что при использовании ASP. NET Core обычно не требуется вручную создавать новую область, каждый веб-запрос автоматически получает собственную область. Все ваши классы автоматически запрашиваются из области, и эта область автоматически очищается в конце веб-запроса.
...