我几乎正在使用Microsoft文档中提供的示例来排队后台任务。
在这个队列中,我正在添加一个Func<Task>
,稍后由QueuedHostedService
执行。
控制器 - HTTP 开机自检
...
Func<Task> workItem = () => _mockService.DoWorkAsync(guid);
_queue.QueueBackgroundWorkItem(workItem);
return Ok();
MockService.DoWorkAsync
var data = await _insideMockService.GetAsync();
await _anotherService.Notify(data);
BackgroundTaskQueue.QueueBackgroundWorkItem
private ConcurrentQueue<Func<Task>> _workItems
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(Func<Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
QueuedHostedService.ExecuteAsync
_currentTask = await _queue.DequeueAsync(cancellationToken);
try
{
await _currentTask();
...
BackgroundTaskQueue.DequeueAsync
public async Task<Func<Task>> DequeueAsync(CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItemsById.TryDequeue(out var workItem);
return workItem;
}
Startup.ConfigureServices
services.AddScoped<IMockService, MockService>(); // Implements DoWorkAsync
services.AddScoped<IInsideMockService, InsideMockService>(); // DoWorkAsync requires this dependency
services.AddScoped<IAnotherService, AnotherService>(); // DoWorkAsync requires this dependency
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
IMockService.DoWorkAsync
方法使用scoped
服务。此方法的引用将添加到驻留在singleton
服务中的队列中。此队列稍后由HostedService
读取,该也是singleton
。
DoWorkAsync
中的服务引用是否有可能在由HostedService
处理之前被释放?如果我们排除应用程序正常(或不正常(关闭的情况。
一些可能有一百个请求的本地测试运行(以及一些在DoWorkAsync
中添加Task.Delay
(似乎工作正常,但我不确定我是否遗漏了什么。
这里的主要问题不是对 Func 的引用将被垃圾收集。
问题是它可以释放它使用的任何作用域服务。
请求完成后,将处理其所有一次性内容的范围。如果您的服务将实现 IDisposable - 它们也将被释放。排队的任务将尝试调用已释放的服务并失败。
即使您的服务本身不会实现 IDisposable - 它也可能直接或间接使用将要释放的其他一些 IDisposable 对象(例如,DbContext(。
为了确保这些东西不会在某一天失败 - 你应该仔细控制DoWorkAsync
期间使用的所有对象/服务(及其子对象/子服务(。在这种情况下 - 为什么不将 MockService istelf 注册为单例?:)
后台任务的正确方法是捕获IServiceScopeFactory
实例,并在任何实际作业之前调用 CreateScope((,并从中获取任何作用域服务。