Description
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
Please see the description from #50622
TL;DR; when deriving a BackgroundService
and overriding StopAsync
this fails with ObjectDisposedException
for serviceProvider
during integration testing:
public override async Task StopAsync(CancellationToken token)
{
await base.StopAsync(token);
using var scope = serviceProvider.CreateScope();
}
Where serviceProvider
was injected via constructor.
Expected Behavior
I should be able to resolve services from DI until my stopping is done.
Steps To Reproduce
See full repro here: https://github.com/lostmsu/ServiceProviderDisposedRepro (upgraded to .NET 8)
Exceptions (if any)
Class cleanup failed in ServiceProviderDisposed.Tests.Tests
System.ObjectDisposedException
Cannot access a disposed object.
Object name: 'IServiceProvider'.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.CreateScope(IServiceProvider provider)
at ServiceProviderDisposed.DummyBackgroundService.StopAsync(CancellationToken cancellationToken) in C:\Users\brome\Projects\Play\Bugs\ServiceProviderDisposedRepro\ServiceProviderDisposed\DummyBackgroundService.cs:line 24
at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)
at Microsoft.Extensions.Hosting.Internal.Host.StopAsync(CancellationToken cancellationToken)
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.DisposeAsync()
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.Dispose(Boolean disposing)
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.Dispose()
at Xunit.Sdk.ExceptionAggregator.Run(Action code) in /_/src/xunit.core/Sdk/ExceptionAggregator.cs:line 73
.NET Version
8.0.401
Anything else?
The original bug report suggested to implement IHostedLifecycleService
, but moving
using var scope = serviceProvider.CreateScope();
to StoppedAsync
does not prevent the issue.
The scenario I have in mind is to do something after my main service procedure ends (e.g. finalize database recording).
Workaround
Just found a workaround: you can move the necessary code at the end of
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(50);
}
DoYourThingHere();
}
The problem with that though is that you lose access to the CancellationToken
from StopAsync
: the one in ExecuteAsync
has completely different purpose.