Search code examples
c#.netdependency-injectionidisposableiasyncdisposable

How to dispose a service implementing IAsyncDisposable using Dependency Injection in .NET 7


I am having a service that uses the IAsyncDisposable interface, because it holds an object that has the interface itself.

public class AddressImporter : IAddressImporter
{
    private readonly IConsumer<Model> _consumer; // implementing IAsyncDisposable
}

I register the service like this: services.AddTransient<IAddressImporter, AddressImporter>(); And inject it into my main service like this:

public MainService(IAddressImporter addressImporter) {
    _addressImporter = addressImporter;
}

Will this leak the resource? If so how can I improve this, so DisposeAsync is called on any exception in my main service?

I was thinking of adding IAsyncDisposable to IAddressImporter and calling IAddressImporter.DisposeAsync on any exception or the end of my MainService.

EDIT: When trying to use the service

public class AddressImporter : IAddressImporter
{
    private readonly IConsumer<Model> _consumer; // implementing IAsyncDisposable
    public async ValueTask DisposeAsync()
    {
        _logger.Information("Disposing {ResourceName}", nameof(PulsarAddressImporter));
        await _consumer.DisposeAsync();
        GC.SuppressFinalize(this);
    }

}

I get the exception:

BackgroundService failed System.InvalidOperationException: 'AddressService.Web.Jobs.Test' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container. System.InvalidOperationException: 'AddressService.Web.Jobs.Test' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container. [15:23:56 INF] Application is shutting down...


Solution

  • Will this leak the resource?

    It depends on lifecycle of the parent object and scope - services created by build-in DI container are disposed automatically when the scope is disposed:

    class MyTransient : IAsyncDisposable
    {
        public bool Disposed { get; set; }
        public ValueTask DisposeAsync()
        {
            Disposed = true;
            return ValueTask.CompletedTask;
        }
    }
    
    var services = new ServiceCollection();
    services.AddTransient<MyTransient>();
    
    var serviceProvider = services.BuildServiceProvider();
    MyTransient myTransient;
    await using (var scope = serviceProvider.CreateAsyncScope())
    {
        myTransient = scope.ServiceProvider.GetRequiredService<MyTransient>();
    }
    
    Console.WriteLine(myTransient.Disposed); // prints True
    

    So unless the service is created from the root one it will disposed with everything else when the owner scope ends, so making AddressImporter implementing IDisposable/IAsyncDisposable should do the trick (if the exception leads to end of the owner scope).