Как разрешить службы в фоновой задаче?

Я реализовал 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));

Но это считается анти-шаблоном.


person Ivan-Mark Debono    schedule 30.03.2020    source источник
comment
Вам нужно будет реорганизовать очередь, чтобы она зависела от контейнера, и иметь ее в качестве параметра делегата функции.   -  person Nkosi    schedule 30.03.2020


Ответы (1)


Вам придется управлять своими собственными 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();
}

Вы можете получить новый ILifetimeScope, используя BeginLifetimeScope существующей области действия, а ILifetimeScope является зарегистрированной службой.

Если вы используете реализацию 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, чтобы упростить задачу.

person Cyril Durand    schedule 30.03.2020
comment
Все правильные решения, и я уже думал о первом. Я пойду этим путем. Службы разрешаются в задаче в новых экземплярах или в общем экземпляре (поскольку в настоящее время я регистрирую их как InstancePerLifetimeScope)? - person Ivan-Mark Debono; 31.03.2020
comment
поскольку вы создаете новый жизненный диапазон, используя BeginLifetimeScopeи используете InstancePerLifetimeScope, у вас будет новый экземпляр для каждой задачи - person Cyril Durand; 31.03.2020