如何配置简单的喷油器运行在ASP.NET MVC后台线程(How to configure Simp

2019-06-25 11:58发布

我使用简单的喷油器来管理我的注入依赖的寿命(在这种情况下UnitOfWork ),和我一样有一个单独的装饰,而不是我的服务或命令处理程序保存和处理后,看起来很高兴的样子,写业务时,代码容易得多逻辑层(按照所概述的架构这篇博客 )。

以上是通过使用简单的进样器MVC NuGet包可以正常使用(和非常容易)和组合物根容器的结构在以下代码中,如果在图中存在多于一个的依赖性相同的实例在所有注射 - 完美的实体框架模型上下文。

private static void InitializeContainer(Container container)
{
    container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork>();
    // register all other interfaces with:
    // container.Register<Interface, Implementation>();
}

我现在需要运行一些后台线程,并从简单的喷油器了解上线文档,该命令可以如下代理:

public sealed class TransactionCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    private readonly ICommandHandler<TCommand> handlerToCall;
    private readonly IUnitOfWork unitOfWork;

    public TransactionCommandHandlerDecorator(
        IUnitOfWork unitOfWork, 
        ICommandHandler<TCommand> decorated)
    {
        this.handlerToCall = decorated;
        this.unitOfWork = unitOfWork;
    }

    public void Handle(TCommand command)
    {
         this.handlerToCall.Handle(command);
         unitOfWork.Save();
    }
}

ThreadedCommandHandlerProxy:

public class ThreadedCommandHandlerProxy<TCommand>
    : ICommandHandler<TCommand>
{
    Func<ICommandHandler<TCommand>> instanceCreator;

    public ThreadedCommandHandlerProxy(
        Func<ICommandHandler<TCommand>> creator)
    {
        this.instanceCreator = creator;
    }

    public void Handle(TCommand command)
    {
        Task.Factory.StartNew(() =>
        {
            var handler = this.instanceCreator();
            handler.Handle(command);
        });
    }
} 

然而,从这个线程样本文档,我可以看到工厂的使用,如果我介绍的工厂,以我的命令和服务层的东西会感到困惑和矛盾,因为我将有不同的服务不同的储蓄方法(一个容器处理保存,其它实例化的工厂内服务句柄保存和处置) - 你可以看到服务代码框架如何简单明了的是,没有任何工厂:

public class BusinessUnitCommandHandlers :
    ICommandHandler<AddBusinessUnitCommand>,
    ICommandHandler<DeleteBusinessUnitCommand>
{
    private IBusinessUnitService businessUnitService;
    private IInvoiceService invoiceService;

    public BusinessUnitCommandHandlers(
        IBusinessUnitService businessUnitService, 
        IInvoiceService invoiceService)
    {
        this.businessUnitService = businessUnitService;
        this.invoiceService = invoiceService;
    }

    public void Handle(AddBusinessUnitCommand command)
    {
        businessUnitService.AddCompany(command.name);
    }

    public void Handle(DeleteBusinessUnitCommand command)
    {
        invoiceService.DeleteAllInvoicesForCompany(command.ID);
        businessUnitService.DeleteCompany(command.ID);
    }
}

public class BusinessUnitService : IBusinessUnitService
{
    private readonly IUnitOfWork unitOfWork;
    private readonly ILogger logger;

    public BusinessUnitService(IUnitOfWork unitOfWork, 
        ILogger logger)
    {
        this.unitOfWork = unitOfWork;
        this.logger = logger;
    }

    void IBusinessUnitService.AddCompany(string name)
    {
        // snip... let container call IUnitOfWork.Save()
    }

    void IBusinessUnitService.DeleteCompany(int ID)
    {
        // snip... let container call IUnitOfWork.Save()
    }
}

public class InvoiceService : IInvoiceService
{
    private readonly IUnitOfWork unitOfWork;
    private readonly ILogger logger;

    public BusinessUnitService(IUnitOfWork unitOfWork, 
        ILogger logger)
    {
        this.unitOfWork = unitOfWork;
        this.logger = logger;
    }

    void IInvoiceService.DeleteAllInvoicesForCompany(int ID)
    {
        // snip... let container call IUnitOfWork.Save()
    }
}

通过以上我的问题开始形成,我从理解上的ASP .NET PerWebRequest寿命文件 ,下面的代码被用于:

public T GetInstance()
{
    var context = HttpContext.Current;

    if (context == null)
    {
        // No HttpContext: Let's create a transient object.
        return this.instanceCreator();
    }

    object key = this.GetType();
    T instance = (T)context.Items[key];

    if (instance == null)
    {
        context.Items[key] = instance = this.instanceCreator();
    }
    return instance;
}

上述工作正常,每次HTTP请求将会有一个有效的HttpContext.Current ,但是如果我旋了一个新的线程与ThreadedCommandHandlerProxy它会创建一个新的线程和HttpContext将是线程中不再存在。

由于HttpContext将是对每个后续调用空,对象的所有实例注入的服务构造将是新的,独特的,相反,每Web请求正常的HTTP,而对象正确共享作为所有服务相同的实例。

所以总结到上述问题:

我将如何去获取物体构造和无论从HTTP请求或通过一个新的线程创建的常见项目注入?

有没有一种具有任何特殊考虑UnitOfWork命令处理程序代理内由一个线程管理? 如何确保它被保存和设置在处理程序执行之后的?

如果我们有命令处理程序/服务层中的问题,并没有要保存UnitOfWork ,我们会简单地抛出一个异常? 如果是的话,是有可能抓住这个在全球层面,还是我们需要从抓内每个请求例外try - catch在处理装饰或代理?

谢谢,

克里斯

Answer 1:

最后,我要警告说,如果你想在Web应用程序异步执行的命令,你可能要退一步,看看你想达到什么样的启动。 总是有Web应用程序,你在后台线程启动处理程序后,被刚回收的风险。 当ASP.NET应用程序得到回收,所有的后台线程将被中止。 这或许会是更好地发布命令到(事务)队列,并让后台服务去接他们。 这保证了命令不能“迷路”。 而且还允许在处理程序不顺利成功,你重新执行命令。 它也可以使你免于做一些讨厌的注册(这你可能会不管你选择哪个DI框架),但是这可能只是一个次要问题。 而如果你需要运行处理异步,至少尽量减少您运行的异步处理程序的数量。

有了这样的方式,你需要的是以下内容。

正如你提到的,因为你是异步运行的(一些)命令处理程序,您不能使用每个Web请求的生活方式在他们身上。 您将需要一个混合解决方案,每Web请求和“别的东西”之间的混合。 其他的东西很可能将是一个每一生范围 。 有这些混合解决方案,因为几个原因没有内置的扩展。 首先,它是一个很奇特的功能,没有多少人需要。 其次,你可以任意两种或三种生活方式混合在一起,所以这将是混合动力车的几乎无限的组合。 而在去年,它是(很)容易做这个注册自己。

在简单的注射器2, Lifestyle类已经被添加,它包含一个CreateHybrid方法,允许合并任何两个生活方式创造一种新的生活方式。 下面是一个例子:

var hybridLifestyle = Lifestyle.CreateHybrid(
    () => HttpContext.Current != null,
    new WebRequestLifestyle(),
    new LifetimeScopeLifestyle());

您可以使用这种混合的生活方式注册工作的单位:

container.Register<IUnitOfWork, DiUnitOfWork>(hybridLifestyle);

由于您注册工作的单位为每终生范围,你必须明确地创建和部署一个终身范围为某线程。 做最简单的事情就是添加到您的ThreadedCommandHandlerProxy 。 这还不是最SOLID做事的方式,但它是我向您展示如何做到这一点的最简单的方法。

如果我们有命令处理程序/服务层中的问题,并没有要保存的UnitOfWork,我们会简单地抛出一个异常?

做典型的就是抛出异常。 这是事实上的例外的一般规则:

如果你的方法不能做什么它的名字承诺是可以的,扔。 - >

命令处理程序应该是一无所知其所执行的背景下,和你想的最后一件事是如何在是否应该抛出一个异常分化。 所以,投掷是您最佳的选择。 然而,当在后台线程运行时,你最好捕获了异常,因为.NET将杀死全部的AppDomain,如果你不抓住它。 在Web应用程序,这意味着一个AppDomain的回收,这意味着你的Web应用程序(或至少是服务器)将针对短时间内处于脱机状态。

在另一方面,你也不想失去任何异常信息,所以你应该日志例外,而且可能要登录该命令的序列化表示,除此之外,所以你可以看到在传递什么样的数据。当加入ThreadedCommandHandlerProxy.Handle方法中,它是这样的:

public void Handle(TCommand command)
{
    string xml = this.commandSerializer.ToXml(command);    

    Task.Factory.StartNew(() =>
    {
        var logger = 
            this.container.GetInstance<ILogger>();

        try
        {
            using (container.BeginTransactionScope())
            {
                // must be created INSIDE the scope.
                var handler = this.instanceCreator();
                handler.Handle(command);
            }
        }
        catch (Exception ex)
        {
            // Don't let the exception bubble up, 
            // because we run in a background thread.
            this.logger.Log(ex, xml);
        }
    });
}

我警告过有关如何运行处理异步可能不是最好的主意。 但是,因为你申请这个命令/处理模式,你就可以切换到使用排队以后,而无需改变的一行代码在应用程序中。 这只是一个写某种的事QueueCommandHandlerDecorator<T>其中序列化的命令,并将其发送到队列)和改变的东西在你的作文根连线的方式,你是好去(当然不要”别忘了实现从队列中执行命令的服务)。 换句话说,什么是伟大的关于这个坚实的设计是实现这些功能是恒定的应用程序的大小。



Answer 2:

部分在ASP.NET运行的后台线程的问题都可以迎刃而解了一个方便的命令处理程序装饰:

public class AspNetSafeBackgroundCommandHandlerDecorator<T>
    : ICommandHandler<T>, IRegisteredObject
{
    private readonly ICommandHandler<T> decorated;

    private readonly object locker = new object();

    public AspNetSafeBackgroundCommandHandlerDecorator(
        ICommandHandler<T> decorated)
    {
        this.decorated = decorated;
    }

    public void Handle(T command)
    {
        HostingEnvironment.RegisterObject(this);

        try
        {
            lock (this.locker)
            {
                this.decorated.Handle(command);
            }
        }
        finally
        {
            HostingEnvironment.UnregisterObject(this);
        }            
    }

    void IRegisteredObject.Stop(bool immediate)
    {
        // Ensure waiting till Handler finished.
        lock (this.locker) { }
    }
}

当你把一个命令处理程序和之间的这个装饰ThreadedCommandHandlerProxy ,你会确保这样的命令运行,而(正常情况下)的AppDomain不卸载。



文章来源: How to configure Simple Injector to run background threads in ASP.NET MVC