0

我几乎使用 Microsoft 文档中提供的示例来排队后台任务

在这个队列中,我添加了一个Func<Task>, 稍后由QueuedHostedService.

控制器 - HTTP POST

...
Func<Task> workItem = () => _mockService.DoWorkAsync(guid);
_queue.QueueBackgroundWorkItem(workItem);
return Ok();

模拟服务.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;
}

启动.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也是 a的 a 读取singleton

是否有可能在DoWorkAsync处理之前处理服务引用HostedService?如果我们排除应用程序正常(或不正常)关闭的场景。

一些可能有一百个请求的本地测试运行(以及一些添加Task.Delay的请求DoWorkAsync)似乎工作正常,但我不确定我是否遗漏了什么..

4

1 回答 1

1

这里的主要问题不是对 Func 的引用会被垃圾收集。

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

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

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

为了确保这些东西在某一天不会失败 - 你应该仔细控制在DoWorkAsync. 在这种情况下 - 为什么不将 MockService istelf 注册为单例?:)

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

于 2019-09-23T15:44:14.787 回答