如何解决后台任务中的服务?

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();
}

backgroundQueueIBackgroundQueue 的一个实例,并在 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 这样的专用框架来简化操作。