【问题标题】:Unity LifeTime manager for Quartz.netQuartz.net 的 Unity LifeTime 管理器
【发布时间】:2016-12-06 03:28:08
【问题描述】:

我正在尝试在 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 作业中正确管理依赖生命周期?

【问题讨论】:

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


    【解决方案1】:

    我对 Castle.Windsor 和 Quartz.Net 也有同样的问题。我发现唯一适用的方法是 ScopedLifetime,但您必须自己控制 Scope。如果有新的 Request 进来,打开一个范围,所有服务都将在这个范围内解析(所谓的 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. 你的 Job 获得了一个 Factory 或其他可以启动范围和服务的东西,例如:Func&lt;IFooRepository&gt; repositoryFunc。然后在您的 Execute 方法中,启动范围(再次使用 using)并调用您的 repository,它将返回您在该范围内的真实存储库,您可以随心所欲地使用它。这应该是最好的方法。请注意,这不被视为服务定位器模式,因为您将服务的 Func&lt;&gt; 分配给您的工作,而您只需控制范围。

      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
              }
          }
      }
      

    问题

    1. PerThreadLifetimeManager

      操作无法完成,因为 DbContext 已被释放。

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

    2. PerResolveLifeTimeManager

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

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

    【讨论】:

    • 感谢您的解释。我还没有完全掌握如何实现这个工厂,但我明天会继续努力。如果你有任何例子,那就太好了。是否可以在我的 UnityJobFactory 中打开和关闭这些范围?这样我就可以让工作对工厂一无所知。
    【解决方案2】:

    很久以前就用 simpleinjector 让它工作了。 不过这是使用旧版本的 Quartz,希望它仍然可以提供帮助

    您需要创建一个自定义 LifetimeScope

    public class LifetimeScopeJobDecorator : IJob
    {
        private readonly IJob _decoratee;
        private readonly Container _container;
    
        public LifetimeScopeJobDecorator(IJob decoratee, Container container)
        {
            _decoratee = decoratee;
            _container = container;
        }
    
        public void Execute(IJobExecutionContext context)
        {
                using (_container.BeginLifetimeScope())
                {
                    _decoratee.Execute(context);
                }
        }
    }
    

    你打电话到你的工作工厂

    public class SimpleInjectorJobFactory : IJobFactory
    {
        private readonly Container _container;
    
        public SimpleInjectorJobFactory(Container container)
        {
            _container = container;
            _container.Verify();
        }
    
        public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        {
                IJobDetail jobDetail = bundle.JobDetail;
                Type jobType = jobDetail.JobType;
                var job = (IJob)_container.GetInstance(jobType);
                return new LifetimeScopeJobDecorator(job, _container);
        }
    
        public void ReturnJob(IJob job)
        {
        }
    }
    

    然后你就可以初始化你的自定义 Quartz Container

    public static class QuartzScheduler
    {
        private static Container _quartzContainer { get; set; }
    
        private static void Initialize()
        {
            Container container = new Container();
            container.RegisterLifetimeScope<IUnitOfWork, SqlUnitOfWork>();
            container.Register<ILogger, NLogLogger>();
    
    
            //To enable lifetime scoping, please make sure the EnableLifetimeScoping extension method is called during the configuration of the container.
            container.EnableLifetimeScoping();
            container.Verify();
            _quartzContainer = new Container();
    
            var schedulerFactory = new StdSchedulerFactory();
            _quartzContainer.RegisterSingle<IJobFactory>(() => new SimpleInjectorJobFactory(container));
            _quartzContainer.RegisterSingle<ISchedulerFactory>(schedulerFactory);
            _quartzContainer.Register<IScheduler>(() =>
            {
                var scheduler = schedulerFactory.GetScheduler();
                scheduler.JobFactory = _quartzContainer.GetInstance<IJobFactory>();
                return scheduler;
            }
            );
            _quartzContainer.Verify();
    

    启动调度器

        public static void StartJobs()
        {
            Initialize();
    
                //Ask the scheduler factory for a scheduler
                IScheduler scheduler = _quartzContainer.GetInstance<IScheduler>();
                scheduler.Start();
    
        }
    

    【讨论】:

      【解决方案3】:

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

      除了上面的 nuget 包之外,如果你使用多个统一容器实例并将生命周期管理器作为委托传递,它将允许你正确处理每个石英作业以及每个 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 中配置方法的实现。在这里,我注册了 IHelloService 以使用disposableLifetimeManager 委托。调用委托时,会根据您的执行路径提供适当的生命周期管理器。如果在 asp.net mvc / web api 的上下文中使用 IHelloService,它将是 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 实现不足以满足您的用例,您可以随时对其进行进一步定制。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-17
        • 1970-01-01
        • 2014-07-12
        • 1970-01-01
        相关资源
        最近更新 更多