1

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...

Steven
  • 166,672
  • 24
  • 332
  • 435
TeaJ
  • 25
  • 4
  • Services created by the DI container are disposed by the DI container automatically. How is `_consumer` created? If it's created by DI, it will be disposed automatically. If it's created by `AddressImporter`, that class's `Dispose` or `DisposeAsync` should also dispose `_consumer` – Panagiotis Kanavos Mar 29 '23 at 09:53
  • This works for me with Dispose, but not with IAsyncDisposable When using IAsyncDisposable I get an Exception telling me to call DisposeAsync. It's created by DI. – TeaJ Mar 29 '23 at 13:15
  • Post the actual *full* exception in the question itself. What's `AddressImporter`'s actual code? Does it implement `IAsyncDisposable` or `IDisposable`? – Panagiotis Kanavos Mar 29 '23 at 13:18

1 Answers1

4

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).

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • This works for me with Dispose, but not with IAsyncDisposable When using IAsyncDisposable I get an Exception telling me to call DisposeAsync – TeaJ Mar 29 '23 at 13:15
  • @TeaJ what project type do you have? ASP.NET Core? Console? WPF/WinForms/MAUI? The provided code works, the mentioned exception as far as I remember will happen if `using (var scope = serviceProvider.CreateScope())` is used. – Guru Stron Mar 29 '23 at 13:16
  • 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... – TeaJ Mar 29 '23 at 13:26
  • My example was: public interface ITest { } public class Test: ITest, IAsyncDisposable { private readonly ILogger _logger; public Test(ILogger logger) { _logger = logger; } public async ValueTask DisposeAsync() { _logger.Information("disposed"); } } services.AddTransient(); – TeaJ Mar 29 '23 at 13:26
  • I am using ASP.NET Core Web Application – TeaJ Mar 29 '23 at 13:27
  • @TeaJ `BackgroundService` usually means a singleton lifetime. Are you creating a scope inside it? – Guru Stron Mar 29 '23 at 13:28
  • Ah ok, I am using a bigger library that seems to create singletons. – TeaJ Mar 29 '23 at 13:32
  • @TeaJ what is triggering the disposal? Can you share some [mre]? Either way, if the library is out of your control - you will need to implement `IDisposable` or dispose manually (maybe moving everything into factory). – Guru Stron Mar 29 '23 at 13:38
  • Well I would hope that the DI container, would trigger the disposal. Sharing a minimal reproducible example is hard, because of the library being closed source. Implementing IDisposable and calling _consumer.DisposeAsync().AsTask().Wait(); seems to work. The factory is another possibility I was thinking of. But it is good to know that it normally should be working. – TeaJ Mar 29 '23 at 13:48