在一段Task中,运行使用构造函数注入的IServiceProvider,再通过GetRequiredService获取Service实例时,报以下错误,
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'IServiceProvider'.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
代码结构大概是这样的,大意是使用Task每次运行10个任务,而aService本身是通过定时任务调用的。
public class aService
{
private readonly IServiceProvider _serviceProvider;
public aService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task ExecuteAsync()
{
int maxParallelTasks = 10; // 限制并行任务的数量
SemaphoreSlim semaphore = new SemaphoreSlim(maxParallelTasks);
ConcurrentBag<Task> tasks = new ConcurrentBag<Task>();
foreach (var xxx in xxxList)
{
tasks.Add(Task.Run(async () =>
{
// 进入信号量,减少可用许可数
await semaphore.WaitAsync();
try
{
using var scope = _serviceProvider.CreateScope();
var xxxxService = scope.ServiceProvider.GetRequiredService<xxxxService>();
var data = await xxxxService.GetDyndatasAsync(xx);
if (data!= null && data.Count > 0)
{
//执行内容。。。。
}
}
catch (Exception ex)
{
//异常处理。。。。
}
finally
{
// 释放信号量,增加可用许可数
semaphore.Release();
}
}));
}
// 等待所有任务完成
await Task.WhenAll(tasks);
}
}
最上层的Job调用大概是这样的,aService是Transient服务类型,按理说每次调用注入的IServiceProvider 应该是不一样的
public class xxTaskJob : IJob
{
private readonly aService _aService;
private readonly ILogger<xxTaskJob > logger;
public xxJob(IServiceProvider serviceProvider)
{
using var serviceScope = serviceProvider.CreateScope();
_aService= serviceScope.ServiceProvider.GetRequiredService<aService>();
logger = serviceScope.ServiceProvider.GetRequiredService<ILogger<xxTaskJob >>();
}
public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
try
{
await _aService.ExecuteAsync();
}
catch (Exception ex)
{
logger.LogError(ex, "执行ExecuteAsync时发生异常");
}
logger.LogInformation("执行完成");
}
}
问题原因:
IServiceProvider 被提前释放
在 Task.Run 中,通过 _serviceProvider.CreateScope() 创建了一个作用域(IServiceScope),但可能在任务执行时,外部的 IServiceProvider 已经被释放(例如,父容器被销毁)。
常见场景:如果 aService 本身是由依赖注入容器管理的,并且它的生命周期较短(如 Scoped 或 Transient),而任务是异步执行的,可能导致服务提供者在任务运行时已被释放。
根本原因
IServiceProvider 的生命周期管理不当,导致异步任务尝试访问已被释放的服务。
解决方案:
思路1、依赖注入容器的生命周期
在 ASP.NET Core 中,IServiceProvider 可能是分层级的(根容器 + 子容器)。 如果 aService 是 Scoped 服务,它的 _serviceProvider 可能是子容器,而子容器在请求结束时会被释放。 但异步任务可能在请求结束后才执行,导致访问已释放的子容器。
思路2、Task.Run 的陷阱
Task.Run 会将代码调度到线程池执行,但不会自动延长依赖注入容器的生命周期。
因此,必须确保任务内部使用的服务提供者是根容器或独立生命周期的实例。
方案 1:延长 IServiceProvider 的生命周期
确保 aService 的实例生命周期足够长(如 Singleton),避免在任务执行前被释放。
但这种方法可能不符合业务逻辑(例如,如果服务本身应该是 Scoped 的)。
方案 2:在任务内部解析根服务提供者(推荐)
如果 _serviceProvider 是子容器(如 IServiceScope 的 ServiceProvider),则它可能依赖外部容器的生命周期。在构造函数中注入根 IServiceProvider(通常通过 IServiceProvider 的根容器获取,如 IServiceScopeFactory 或直接注入根容器)。
或者,在任务内部重新解析根服务提供者。
用人话来说,就是xxJob通过IServiceProvider获取的aService使用Task异步调用任务时,aService内部窗口使用的是xxJob产生的子容器,当xxJob异步调用后会立即结束,而Task还在继续运行,因此导致xxJob的子容器被提前释放。
使用方案2将aService中的IServiceProvider改为IServiceScopeFactory即可获取根容器,改进后的代码:
public class aService
{
private readonly IServiceScopeFactory _serviceFactory;
public aService(IServiceScopeFactory serviceFactory)
{
_serviceFactory = serviceFactory;
}
public async Task ExecuteAsync()
{
int maxParallelTasks = 10; // 限制并行任务的数量
SemaphoreSlim semaphore = new SemaphoreSlim(maxParallelTasks);
ConcurrentBag<Task> tasks = new ConcurrentBag<Task>();
foreach (var xxx in xxxList)
{
tasks.Add(Task.Run(async () =>
{
// 进入信号量,减少可用许可数
await semaphore.WaitAsync();
try
{
using var scope = _serviceFactory.CreateScope();
var xxxxService = scope.ServiceProvider.GetRequiredService<xxxxService>();
var data = await xxxxService.GetDyndatasAsync(xx);
if (data!= null && data.Count > 0)
{
//执行内容。。。。
}
}
catch (Exception ex)
{
//异常处理。。。。
}
finally
{
// 释放信号量,增加可用许可数
semaphore.Release();
}
}));
}
// 等待所有任务完成
await Task.WhenAll(tasks);
}
}