将具有作用域服务依赖项的任务放在单例队列中是否安全?
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(),并从中获取任何作用域服务。
我几乎使用 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(),并从中获取任何作用域服务。