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

Is it safe putting a Task, with scoped service dependencies, in a Singleton queue?

我几乎使用 Microsoft 文档中为 queuing background tasks 提供的示例。

在此队列中,我添加了一个 Func<Task>,稍后由 QueuedHostedService 执行。

控制器 - HTTP POST

...
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 的引用会被垃圾回收。

问题是它使用的任何作用域服务都可以被释放。

当请求完成时,scope 将与他的所有一次性内容一起处理。如果您的服务将实施 IDisposable - 它们也将被处置。您排队的任务将尝试调用已处理的服务并失败。

即使您的服务本身不会实现 IDisposable - 它也可能直接或间接使用一些其他 处理的 IDisposable 对象(例如,DbContext)。

为了确保这些东西不会在某一天失败 - 你应该小心控制在 DoWorkAsync 期间使用的所有 objects/services(及其 sub-objects/sub-services)。在这种情况下 - 为什么不将 MockService istelf 注册为单身人士? :)

后台任务的正确方法是捕获 IServiceScopeFactory 实例,并在任何实际作业之前调用 CreateScope(),并从中获取任何作用域服务。