如何解决后台任务中的服务?
How to resolve services within a background task?
我已经按照 here 的说明实现了 BackgroundQueue。这将替换旧的 HostingEnvironment。此外,我正在重构代码以使用 Autofac 将服务注入后台任务。
目前代码是这样的:
public ActionResult SomeAction()
{
backgroundQueue.QueueBackgroundWorkItem(async ct =>
{
//Need to resolve services here...
}
return Ok();
}
backgroundQueue
是 IBackgroundQueue
的一个实例,并在 Autofac 中注册为单例。
如何将 Autofac 容器传递给任务以便注册服务?或者有没有更好的方法在任务中注册服务?
一个解决方案可能是这样做的:
var myService = HttpContext.RequestServices.GetService(typeof(IMyService));
但这被认为是反模式。
您必须在任务中管理自己的 LifetimeScope
。
最简单的方法是更改方法 QueueBackgroundWorkItem
以引入 ILifetimeScope
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<ILifetimeScope, CancellationToken, Task> workItem);
然后
public ActionResult SomeAction()
{
backgroundQueue.QueueBackgroundWorkItem(async (scope, ct) =>
{
scope.Resolve<IService>().Do()
//Need to resolve services here...
}
return Ok();
}
您可以使用现有范围的 BeginLifetimeScope
获得新的 ILifetimeScope
,并且 ILifetimeScope
是注册服务。
如果您使用 link 提供的 QueueHostedService
实现,您可以按以下方式更改它
public class QueueHostedService: IBackgroundTaskQueue {
public QueueHostedService(ILifetimeScope scope, ...) {
this._rootScope = scope;
}
private readonly ILifetimeScope _rootScope;
...
private async Task BackgroundProcessing(...) {
...
try {
using(ILifetimeScope queueScope = this._rootScope.BeginLifetimeScope()){
await workItem(queueScope, stoppingToken);
}
}
...
}
如果无法更改方法定义,则可以在任务内创建生命周期范围。
您可以在控制器中注入一个 ILifetimeScope
但您不能从中创建一个 LifetimeScope,因为它将在请求结束时被释放。您可以解析一个命名的生命周期范围,它将成为您所有队列生命周期范围的根
public class XController {
public XController(ILifetimeScope scope){
// you can also inject directly the named scope using named attribute or custom parameter, etc.
this._taskRootScope.ResolveNamed<ILifetimeScope>("taskRoot");
}
private readonly ILifetimeScope _taskRootScope;
public ActionResult SomeAction()
{
var taskRootScope = this._taskRootScope;
backgroundQueue.QueueBackgroundWorkItem(async ct =>
{
using(var taskScope = taskRootScope.BeginLifetimeScope()){
taskScope.Resolve<IService>().Do();
}
}
return Ok();
}
}
并且注册将类似于
builder.Register(c => c.Resolve<ILifetimeScope>())
.Named<ILifetimeScope>("taskRoot")
.SingleInstance();
还有很多其他方法可以自己处理范围。
下一步可能是像 ASP.net 核心那样使用方法参数注入,这将导致类似的结果:
backgroundQueue.QueueBackgroundWorkItem(async (IService service, CancellationToken ct) =>
{
//Need to resolve services here...
}
但这需要大量工作
您也可以考虑使用像 hangfire 这样的专用框架来简化操作。
我已经按照 here 的说明实现了 BackgroundQueue。这将替换旧的 HostingEnvironment。此外,我正在重构代码以使用 Autofac 将服务注入后台任务。
目前代码是这样的:
public ActionResult SomeAction()
{
backgroundQueue.QueueBackgroundWorkItem(async ct =>
{
//Need to resolve services here...
}
return Ok();
}
backgroundQueue
是 IBackgroundQueue
的一个实例,并在 Autofac 中注册为单例。
如何将 Autofac 容器传递给任务以便注册服务?或者有没有更好的方法在任务中注册服务?
一个解决方案可能是这样做的:
var myService = HttpContext.RequestServices.GetService(typeof(IMyService));
但这被认为是反模式。
您必须在任务中管理自己的 LifetimeScope
。
最简单的方法是更改方法 QueueBackgroundWorkItem
以引入 ILifetimeScope
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<ILifetimeScope, CancellationToken, Task> workItem);
然后
public ActionResult SomeAction()
{
backgroundQueue.QueueBackgroundWorkItem(async (scope, ct) =>
{
scope.Resolve<IService>().Do()
//Need to resolve services here...
}
return Ok();
}
您可以使用现有范围的 BeginLifetimeScope
获得新的 ILifetimeScope
,并且 ILifetimeScope
是注册服务。
如果您使用 link 提供的 QueueHostedService
实现,您可以按以下方式更改它
public class QueueHostedService: IBackgroundTaskQueue {
public QueueHostedService(ILifetimeScope scope, ...) {
this._rootScope = scope;
}
private readonly ILifetimeScope _rootScope;
...
private async Task BackgroundProcessing(...) {
...
try {
using(ILifetimeScope queueScope = this._rootScope.BeginLifetimeScope()){
await workItem(queueScope, stoppingToken);
}
}
...
}
如果无法更改方法定义,则可以在任务内创建生命周期范围。
您可以在控制器中注入一个 ILifetimeScope
但您不能从中创建一个 LifetimeScope,因为它将在请求结束时被释放。您可以解析一个命名的生命周期范围,它将成为您所有队列生命周期范围的根
public class XController {
public XController(ILifetimeScope scope){
// you can also inject directly the named scope using named attribute or custom parameter, etc.
this._taskRootScope.ResolveNamed<ILifetimeScope>("taskRoot");
}
private readonly ILifetimeScope _taskRootScope;
public ActionResult SomeAction()
{
var taskRootScope = this._taskRootScope;
backgroundQueue.QueueBackgroundWorkItem(async ct =>
{
using(var taskScope = taskRootScope.BeginLifetimeScope()){
taskScope.Resolve<IService>().Do();
}
}
return Ok();
}
}
并且注册将类似于
builder.Register(c => c.Resolve<ILifetimeScope>())
.Named<ILifetimeScope>("taskRoot")
.SingleInstance();
还有很多其他方法可以自己处理范围。
下一步可能是像 ASP.net 核心那样使用方法参数注入,这将导致类似的结果:
backgroundQueue.QueueBackgroundWorkItem(async (IService service, CancellationToken ct) =>
{
//Need to resolve services here...
}
但这需要大量工作
您也可以考虑使用像 hangfire 这样的专用框架来简化操作。