[OR]如何为http请求和石英作业使用UoW定义StructureMap生命周期
我有这个使用SM进行IoC的Web应用程序。我正在使用HybridHttpOrThreadLocalScoped范围来存储我的nHibernate ISession对象。这适用于我的Web请求的每个请求时段的会话。
但我也有quartz.net安排几个工作。该作业使用相同的工作单元来获取ISession对象。在这种情况下,当调度程序启动作业时,一切正常,并且作业运行良好几次UNTIL作业线程ID重复。
想象一下,当作业被调度时,它开始在具有id 11,12,13的线程中运行,然后再次使用线程ID 11。此时,structuremap返回一个已经处理好的会话对象,我得到“System.ObjectDisposedException:Session is closed!”错误。
从我所看到的,会话保存在线程本地存储中,在我的工作单元结束后处理会话后,会话对象仍保留在线程本地存储中。 似乎在线程终止后,其本地存储未被清除,并且在某种程度上创建了具有相同id的新线程时,structmap在旧线程本地存储中查找会话(应该是清除了我相信的新线程并返回已经处理好的会话对象。
问题:
我希望我明白我的问题。这花费了我几个小时的时间,但我还没有找到解决办法。 我感谢任何提示:>
更新 我添加了自己的解决方案,使StructureMap服务的UoW兼具http请求和石英作业。如果您有更好/更容易/更简单的解决方案,请告诉我。
答案 0 :(得分:3)
我正在重新审视我所做的工作,使每个Http和UoW每个石英作业使用Structure UoW,我决定在这里分享我的解决方案。
所以我的想法是,当有一个http上下文时,我想使用StructureMap Hybrid范围来获取UoW的实例,并且当没有http上下文时,每个线程也会获得一个不同的UoW实例(比如当一个石英作业触发时) )。像这样:
For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<UnitOfWork>();
http的UoW工作得很好。问题是每个线程UoW。
以下是发生的事情。当quratz作业触发时,它从线程池中拉出一个线程并使用该线程开始执行该作业。当工作开始时,我请求一个UoW。 StructureMap在本地存储下查找该线程以返回UoW,但因为它无法找到任何实例化并将其保存在线程的本地存储下。我得到UoW,然后执行Begin,Commit,Dispose,一切都很好。
当从线程池中提取线程时发生问题,该线程池之前用于触发作业(并使用UoW)。在您请求UoW时,StructureMap会在缓存(线程本地存储)中查找并找到UoW并将其返回给您。但问题是UoW被处置了!
因此,我们无法在每个线程中使用UoW进行石英作业,因为线程本身并未处理,并且它们保留旧的缓存处理UoW。 线程的生命周期基本上与石英作业的生命周期不匹配。这就是我为石英作业创建自己的生命周期的原因。
首先,我创建了自己的http-quartz混合生命周期类:
public class HybridHttpQuartzLifecycle : HttpLifecycleBase<HttpContextLifecycle, QuartzLifecycle>
{
public override string Scope { get { return "HybridHttpQuartzLifecycle"; } }
}
然后我创建了QuartzLifecyle类:
public class QuartzLifecycle : ILifecycle
{
public void EjectAll()
{
FindCache().DisposeAndClear();
}
public IObjectCache FindCache()
{
return QuartzContext.Cache;
}
public string Scope { get { return "QuartzLifecycle"; } }
}
然后我需要为Quartz创建一些像HttpContext这样的上下文类来保存与上下文相关的信息。所以我创建了QuartzContext类。 触发石英作业时,该作业的JobExecutionContext应在QuartzContext中注册。然后,将在该特定JobExecutionContext下创建StructureMap实例的实际缓存(MainObjectCache)。所以这种方式在作业执行完成后,缓存也会消失,我们也不会遇到缓存中处理UoW的问题。
由于_jobExecutionContext是ThreadStatic,当我们从QuartzContext请求缓存时,它将从为同一个线程保存的JobExecutionContext返回缓存。因此,当多个作业同时运行时,它们的JobExecutionContexts将单独保存,并且每个正在运行的作业都有单独的缓存。
public class QuartzContext
{
private static readonly string _cacheKey = "STRUCTUREMAP-INSTANCES";
[ThreadStatic]
private static JobExecutionContext _jobExecutionContext;
protected static void Register(JobExecutionContext jobExecutionContext)
{
_jobExecutionContext = jobExecutionContext;
_jobExecutionContext.Put(_cacheKey, new MainObjectCache());
}
public static IObjectCache Cache
{
get
{
return (IObjectCache)_jobExecutionContext.Get(_cacheKey);
}
}
}
我有一个名为BaseJobSingleSession的抽象类,其他作业来自。这个类扩展了QuartzContext类。你可以看到我在工作被解雇时注册了JobExecutionContext。
abstract class BaseJobSingleSession : QuartzContext, IStatefulJob
{
public override void Execute(JobExecutionContext context)
{
Register(context);
IUnitOfWork unitOfWork = ObjectFactory.GetInstance<IUnitOfWork>();
try
{
unitOfWork.Begin();
// do stuff ....
unitOfWork.Commit();
}
catch (Exception exception)
{
unitOfWork.RollBack();
}
finally
{
unitOfWork.Dispose();
}
}
}
最后,我为UoW定义了生命周期:
For<IUnitOfWork>().LifecycleIs(new HybridHttpQuartzLifecycle()).Use<UnitOfWork>();
(对于生命周期和上下文类,我查看了StructureMap源代码以获得想法。)
请分享您的想法,意见和建议:&gt;
答案 1 :(得分:1)
为什么不为石英作业创建新会话?工作单元通常是db上的事务操作。我无法想象石英作业与Web请求/响应在事务上相关。创建新会话并不昂贵。这有可能吗?