Konieczność zwalniania zasobów za pomocą słowa kluczowego using.

0

Cześć,

Jakiś czas temu postanowiłem przyjrzeć się nieco technologii .NET Core. W związku z tym podjąłem decyzję o zrealizowaniu pewnego projektu ww. technologii. Na chwilę obecną chciałbym utworzyć sobie jakiś test integracyjny. Oto fragmenty mojego kodu:

public class CustomWebApplicationFactory: WebApplicationFactory<MyApp.Startup>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services => 
        {
            var dbContextOptionsServiceDescriptors = services
                .Where(sd => sd.ServiceType.BaseType == typeof(DbContextOptions))
                .ToList();

            foreach (var sd in dbContextOptionsServiceDescriptors)
            {
                services.Remove(sd);
            }

            var databaseName = "MyAppInMemoryDatabaseForTests";
            services.AddDbContext<MainDbContext>(c => c.UseInMemoryDatabase(databaseName));
        });
    }
}


public class SampleTests: 
    IClassFixture<CustomWebApplicationFactory>
{
    private readonly CustomWebApplicationFactory _factory;

    public SampleTests(CustomWebApplicationFactory factory)
    {
        _factory = factory;
    }

    [Fact]
    public void SampleTest()
    {
        using (var scope = _factory.Services.CreateScope()) 
        {
            var service = scope.ServiceProvider.GetRequiredService<IMyService>();
        }
    }
}

Klasa SampleTests zawiera poniższy fragment kodu:

using (var scope = _factory.Services.CreateScope()) 
{
    var service = scope.ServiceProvider.GetRequiredService<IMyService>();
}

Z tego co wiem, powinno się wywoływać metodę Dispose na obiektach implementujących interfejs IDisposable (bezpośrednio lub za pośrednictwem słowa kluczowego using).

Problem jednak w tym, że property Service zwraca obiekt implementujący IServiceProvider, który akurat implementuje też interfejs IDisposable, a metoda CreateScope zwraca kolejny obiekt implementujący IServiceScope, który również akurat implementuje interfejs IDisposable (czyli razem mam dwa obiekty implementujące interfejs IDisposable). W związku z powyższym mam pytanie czy powyższy kod jest poprawny czy należy raczej przekształcić go do poniższej postaci?

using (var serviceProvider = _factory.Services) 
{
    using (var scope = serviceProvider.CreateScope()) 
    {
        var service = scope.ServiceProvider.GetRequiredService<IMyService>();
    }
}

Ponadto załóżmy na chwilę, że metoda CreateScope zwraca obiekt implementujący IServiceScope, który akurat nie implementuję interfejsu IDisposable. Wtedy nie musiałbym używać słowa kluczowego using. Jednak po jakimś czasie ktoś może zmienić kod źródłowy metody CreateScope() w taki sposób aby metoda zwracała obiekt implementujący interfejs IDisposable. Co wtedy? Zasoby nigdy się nie zwolnią? Zasoby zwolnią się ale nie wiadomo kiedy? Jak się przed czymś takim uchronić?

2

Dispose służy do deterministycznego i natychmiastowego zwolnienia zasobów. Jak nie wywołasz Dispose zrobi to za Ciebie GC (poprzez finalizator) kiedy uzna że obiekt jest już nie osiągalny, w skrajnych przypadkach może dojść do timeoutu albo polecieć jakiś wyjątek i rzeczywiście zasoby nie zostaną zwolnione, dlatego określamy ten sposób niedeterministycznym. Jeśli obiekt był w generacji 0 w trakcie uznania za nie osiągalny przez GC to Dispose zostanie wywołane w miarę szybko, jak był w generacji 2 albo LOH no to możemy trochę poczekać na zwolnienie zasobów.

Mówiąc prosto, jak nie wywołasz Dispose tragedii nie będzie.

1

no chyba, że chodzi o plik, który zamknąłeś 30 minut temu a apka nadal trzyma do niego uchwyt...

0

Dispose służy do zwalniania zasobów niezarządzalnych. To jest coś wręcz zupełnie przeciwnego od tego, czym zajmuje się GC.

Tragedii może faktycznie nie będzie, jeśli zaczekamy losowy czas na to, aż GC zwolni zasoby. Będzie zwykła kupa (słabego kodu i potencjalnych problemów). Nie wiem czy warto iść tą drogą.

1 użytkowników online, w tym zalogowanych: 0, gości: 1