StructureMap从线程局部范围返回已处置的nHibenrate会话对象

时间:2010-07-03 01:43:20

标签: .net multithreading nhibernate structuremap quartz.net

[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在旧线程本地存储中查找会话(应该是清除了我相信的新线程并返回已经处理好的会话对象。

问题:

  1. 有没有办法清除线程本地存储(终止时)?
  2. 是否有与线程范围对象相同的“ReleaseAndDisposeAllHttpScopedObjects”?
  3. 有没有办法取消(或弹出)已处置的对象,所以即使SM找到它然后它也找不到并且必须创建一个新实例?
  4. 我希望我明白我的问题。这花费了我几个小时的时间,但我还没有找到解决办法。 我感谢任何提示:>

    更新 我添加了自己的解决方案,使StructureMap服务的UoW兼具http请求和石英作业。如果您有更好/更容易/更简单的解决方案,请告诉我。

2 个答案:

答案 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请求/响应在事务上相关。创建新会话并不昂贵。这有可能吗?