Quartz.net的Unity LifeTime经理

时间:2016-11-24 18:14:54

标签: c# asp.net-mvc entity-framework unity-container quartz.net

我正在尝试在asp.NET MVC应用程序中使用Quartz.Net。我使用Unity作为DI,PerRequestLifeTimeManager

然而,Quartz.Net不能与PerRequestLifeTimeManager一起使用,因为它没有开始的请求。我尝试用它解决的任何依赖都会返回null。

我创建了一个类似适配器的类,根据上下文使用两个生命周期管理器:

class CustomLifetimeManager : LifetimeManager
{
    private readonly string _key = "CustomLifetimeManagerKey" + Guid.NewGuid();
    private readonly PerResolveLifetimeManager _perResolveLifetimeManager = new PerResolveLifetimeManager();

    private bool IsWebContext => HttpContext.Current != null;

    public override object GetValue()
    {
        return IsWebContext 
            ? HttpContext.Current.Items[_key] 
            : _perResolveLifetimeManager.GetValue();
    }

    public override void SetValue(object newValue)
    {
        if (IsWebContext)
            HttpContext.Current.Items[_key] = newValue;
        else
            _perResolveLifetimeManager.SetValue(newValue);
    }

    public override void RemoveValue()
    {
        if (IsWebContext)
            HttpContext.Current.Items[_key] = null;
        else
            _perResolveLifetimeManager.RemoveValue();
    }
}

我已经尝试PerThreadLifetimeManager,它第一次执行正常,然后后续执行失败并显示消息

  

由于DbContext已经完成,因此无法完成操作   地布置。

我已尝试更改为PerResolveLifeTimeManager,但失败了

  

实体对象不能被多个实例引用   IEntityChangeTracker

我的工作非常简单,类似于以下内容:

[DisallowConcurrentExecution]
class MyJob 
{
    IFooRepository _fooRepository;
    IBarRepository _barRepository;
    public MyJob(IFooRepository fooRepository, IBarRepository barRepository)
    {
        _fooRepository = fooRepository;
        _barRepository = barRepository;
    }

    public void Execute(IJobExecutionContext context)
    {
        var foos = _fooRepository.Where(x => !x.Processed);

        foreach(var foo in foos)
        {
            var bar = _barRepository.Where(x => x.Baz == foo.Baz);
            foo.DoMagic(bar);
            foo.Processed = true;
            _fooRepository.Save(foo);
        }
    }
}

我的工作工作是

public class UnityJobFactory : IJobFactory
{
    private readonly IUnityContainer _container;

    public UnityJobFactory(IUnityContainer container)
    {
        _container = container;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        return (IJob)_container.Resolve(bundle.JobDetail.JobType);
    }

    public void ReturnJob(IJob job)
    {

    }
}

如何正确管理Quartz作业中的依赖项生命周期?

2 个答案:

答案 0 :(得分:3)

我在Castle.Windsor和Quartz.Net上遇到了同样的问题。我发现的唯一适用方式是ScopedLifetime,但您必须自己控制Scope。如果有新请求进入,请打开一个范围,所有服务都将在此范围内解析(所谓的UnitOfWork;)),当请求结束时,关闭它。

工作处理更棘手。但是你有两种方法可以解决这个问题。对于这两种方式,您需要一个可以启动范围的工厂。

  1. 您的作业在构造函数中获得一个工厂,在Execute(IJobExecutionContext context)工厂启动一个作用域,解析您的服务(存储库...)执行任何作业并关闭作用域。 using(Factory.BeginScope())对此非常有用。这样做的缺点是,由于使用了服务定位器模式,它被认为是不好的做法。

    public class MyJob
    {
        private readonly Factory Factory;
    
        public MyJob(Factory factory)
        {
            Factory = factory;
        }
    
        public void Execute(IJobExecutionContext context)
        {
            using (Factory.BeginScope())
            {
                var repo = Factory.Create<IFooRepository>();
                // Do stuff
    
                Factory.Release(repo);
            }
        }
    }
    
  2. 您的工作获得工厂或可以启动范围和服务的东西,如:Func<IFooRepository> repositoryFunc。然后在你的Execute方法中,启动范围(再次使用)并调用repository,它将返回该范围内的真实存储库,您可以按照自己的意愿使用它。这应该是最好的方法。请注意,这不被视为服务定位器模式,因为您为作业提供了Func<>,您只需控制范围。

    public class MyJob
    {
        private readonly Factory Factory;
        private readonly Func<IFooRepository> RepositoryFunc;
    
        public MyJob(Factory factory, Func<IFooRepository> repositoryFunc)
        {
            Factory = factory;
            RepositoryFunc= repositoryFunc;
        }
    
        public void Execute(IJobExecutionContext context)
        {
            using (Factory.BeginScope())
            {
                var repo = RepositoryFunc();
                // Do Stuff
            }
        }
    }
    
  3. 问题

    1. PerThreadLifetimeManager

        

      由于已经处理了DbContext,因此无法完成操作。

      这是因为Quartz使用MainThread,默认情况下使用10个Threads的ThreadPool。所有作业都在MainThread中创建,然后在池中的空闲线程中执行。如果您启动作业,则DBContext将绑定到MainThread。当你启动另一个作业时,已经有一个DBContext绑定到这个Thread,无论它是处理还是关闭,LifeTimeManager将解析这个已经使用的上下文。 如果您是第一次启动Job,则Thread是新的,您当前的DBContext绑定到此Thread。当您启动下一个作业并在同一个Thread中执行时,总会有一个DBContext绑定到此Thread。 LifeTimeManager解析了这个已经使用过的上下文,但你不能使用它,因为它已关闭。

    2. PerResolveLifeTimeManager

        

      IEntityChangeTracker的多个实例

      无法引用实体对象

      此问题来自EF。即使您使用相同的构造函数解析不同的服务,您解析的每个服务都会获得一个新的Scope。这导致您使用的每个存储库都有自己的DBContext。并且EF禁止在同一实体中使用不同的DBContexts。

答案 1 :(得分:0)

请查看Quartz.Unity nuget包https://github.com/hbiarge/Quartz.Unity,这个Unity包有一个不错的ScopedLifetime实现。

除了上面的nuget包之外,如果你使用多个统一容器实例并将lifetimemanager作为委托传递,它将允许你为每个石英作业以及每个http请求正确处理一次性类型,如DBContext

您必须为asp.net mvc / web api设置单独的IUnityContainer实例,并为Quartz调度程序设置另一个IUnityContainer实例。

这是一份完整的工作样本 https://github.com/vinodres/DITestingWithQuartz

如果你看一下QuartzStartup.cs,我用它来初始化Quartz Scheduler。为简单起见,假设IHelloService是一次性类型,它必须在每个作业的末尾以及每个http请求的末尾处理。我在这里创建一个单独的实例 IUnityContainer分配给QuartzContainer并从Quartz.Unity nuget包中添加了名为QuartzUnityExtention的新扩展。还调用了我在另一个名为unityconfig.cs的文件中创建的.Configure扩展方法。此方法将Func作为参数。此参数允许您根据执行路径传递不同的生命周期管理器实例。

这是 QuartzStartup.cs

[assembly: OwinStartup(typeof(DiTestingApp.QuartzStartup))]
namespace DiTestingApp
{
    /// <summary>
    /// 
    /// </summary>
    public class QuartzStartup
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(QuartzStartup));
        /// <summary>
        /// Get the hangfire container.
        /// </summary>
        private static readonly Lazy<IUnityContainer> QuartzContainer = new Lazy<IUnityContainer>(() =>
        {
            var container = new UnityContainer();
            container.AddNewExtension<QuartzUnityExtension>();
            container.Configure(() => new HierarchicalLifetimeManager());
            return container;
        });

        /// <summary>
        /// 
        /// </summary>
        /// <param name="app"></param>
        public void Configuration(IAppBuilder app)
        {
            Log.Info("Quartz Startup Intitializing...");
            var container = QuartzContainer.Value;
            InitScheduler(container);
            Log.Info("Quartz Startup Intialization Complete...");

            var properties = new AppProperties(app.Properties);
            var cancellationToken = properties.OnAppDisposing;
            if (cancellationToken != CancellationToken.None)
            {
                cancellationToken.Register(() =>
                {
                    QuartzContainer.Value.Dispose();
                    Log.Info("Quartz container disposed (app pool shutdown).");
                });
            }
        }

        private void InitScheduler(IUnityContainer container)
        {
            try
            {
                var scheduler = container.Resolve<IScheduler>();
                scheduler.Start();

                IJobDetail job = JobBuilder.Create<HelloWorldJob>().Build();

                ITrigger trigger = TriggerBuilder.Create()
                    .WithSimpleSchedule(x => x.WithIntervalInSeconds(20).RepeatForever())
                    .Build();

                scheduler.ScheduleJob(job, trigger);
            }
            catch (Exception ex)
            {
                Log.Error(ex);
            }

        }
    }
}

我对asp.net mvc / web api依赖解析器配置的类似设置。我创建了一个名为UnityMvcActivator.cs的文件,这里当我调用.Configure扩展方法时,我正在传递PerRequestLifetimeManager。

<强> UnityMvcActivator.cs

using System;
using System.Linq;
using System.Web.Http;
using System.Web.Mvc;
using Common.Logging;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Mvc;

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(DiTestingApp.App_Start.UnityWebActivator), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(DiTestingApp.App_Start.UnityWebActivator), "Shutdown")]

namespace DiTestingApp.App_Start
{
    /// <summary>Provides the bootstrapping for integrating Unity with ASP.NET MVC.</summary>
    public static class UnityWebActivator
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(UnityWebActivator));
        /// <summary>
        /// Get the hangfire container.
        /// </summary>
        private static readonly Lazy<IUnityContainer> WebContainer = new Lazy<IUnityContainer>(() =>
        {
            var container = new UnityContainer();
            container.Configure(() => new PerRequestLifetimeManager());
            return container;
        });

        /// <summary>Integrates Unity when the application starts.</summary>
        public static void Start() 
        {
            Log.Info("Web api DI container intializing.");
            var container = WebContainer.Value;

            FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
            FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));

            DependencyResolver.SetResolver(new UnityDependencyResolver(container));

            // TODO: Uncomment if you want to use PerRequestLifetimeManager
            Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));

            var resolver = new Microsoft.Practices.Unity.WebApi.UnityDependencyResolver(container);

            GlobalConfiguration.Configuration.DependencyResolver = resolver;
            Log.Info("Web api DI container intialization complete.");
        }

        /// <summary>Disposes the Unity container when the application is shut down.</summary>
        public static void Shutdown()
        {
            Log.Info("Web api DI container disposing.");
            var container = WebContainer.Value;
            container.Dispose();
        }
    }
}

现在出现了使用IUnityContainer注册类型的部分。以下是UnityConfig.cs中configure方法的实现。在这里,我已经注册了IHelloService来使用disposableLifetimeManager委托。调用委托时,将根据您的执行路径提供适当的生命周期管理器。如果IHelloService与asp.net mvc / web api的上下文一起使用,它将是PerRequestLifetimeManager。如果它在Quartz Job中使用,它将是HierarchicalLifetimeManager。

<强> UnityConfig.cs

using System;
using DiTestingApp.Models;
using Microsoft.Practices.Unity;
using Quartz;
using Testing.Scheduler;

namespace DiTestingApp
{
    /// <summary>
    /// Specifies the Unity configuration for the main container.
    /// </summary>
    public static class UnityConfig
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="container"></param>
        /// <param name="disposableLifetimeManager"></param>
        /// <returns></returns>
        public static IUnityContainer Configure(this IUnityContainer container, Func<LifetimeManager> disposableLifetimeManager )
        {

            container.RegisterType<IJob, HelloWorldJob>();
            container.RegisterType<IHelloService, HelloService>(disposableLifetimeManager());
            return container;
        }
    }
}

HierarchicalLifetimeManager用于Quartz执行路径,因此任何一次性类型都将在每个作业结束时正确处理。

如果Quartz.Unity实现不足以满足您的使用需求,您可以随时对其进行自定义。