将具有作用域服务依赖项的任务放入单一实例队列中是否安全?



我几乎正在使用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((,并从中获取任何作用域服务。

最新更新