我正在尝试在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作业中的依赖项生命周期?
答案 0 :(得分:3)
我在Castle.Windsor和Quartz.Net上遇到了同样的问题。我发现的唯一适用方式是ScopedLifetime,但您必须自己控制Scope。如果有新请求进入,请打开一个范围,所有服务都将在此范围内解析(所谓的UnitOfWork;)),当请求结束时,关闭它。
工作处理更棘手。但是你有两种方法可以解决这个问题。对于这两种方式,您需要一个可以启动范围的工厂。
您的作业在构造函数中获得一个工厂,在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);
}
}
}
您的工作获得工厂或可以启动范围和服务的东西,如: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
}
}
}
问题
PerThreadLifetimeManager
由于已经处理了DbContext,因此无法完成操作。
这是因为Quartz使用MainThread,默认情况下使用10个Threads的ThreadPool。所有作业都在MainThread中创建,然后在池中的空闲线程中执行。如果您启动作业,则DBContext将绑定到MainThread。当你启动另一个作业时,已经有一个DBContext绑定到这个Thread,无论它是处理还是关闭,LifeTimeManager将解析这个已经使用的上下文。 如果您是第一次启动Job,则Thread是新的,您当前的DBContext绑定到此Thread。当您启动下一个作业并在同一个Thread中执行时,总会有一个DBContext绑定到此Thread。 LifeTimeManager解析了这个已经使用过的上下文,但你不能使用它,因为它已关闭。
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实现不足以满足您的使用需求,您可以随时对其进行自定义。