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

发布于 2024-09-08 01:36:51 字数 986 浏览 9 评论 0原文

[OR] 如何定义 UoW 的 StructureMap 生命周期以供 http 请求和quartz 作业使用

我有一个使用 SM 进行 IoC 的 Web 应用程序。我正在使用 HybridHttpOrThreadLocalScoped 范围来存储我的 nHibernate ISession 对象。对于我的网络请求,这在每个请求的会话中工作正常。

但我也有quartz.net 安排了一些工作。该作业使用相同的工作单元来获取 ISession 对象。在这种情况下,当调度程序启动作业时,一开始一切正常,并且作业运行良好几次,直到作业线程 ID 重复。

想象一下,当作业被调度时,它开始在 ID 为 11、12、13 的线程中运行,然后再次在 ID 为 11 的线程中运行。此时,结构图返回一个已释放的会话对象,我得到“System.ObjectDisposeException:会话已关闭!”错误。

因此,从我所看到的,会话保存在线程本地存储中,并且在工作单元结束时处置会话后,会话对象仍然保存在线程本地存储中。 似乎线程终止后,其本地存储不会被清除,并且当创建具有相同 id 的新线程时,StructureMap 会在旧线程本地存储中查找会话(这应该是我相信已清除新线程)并返回已处理的会话对象。

问题:

  1. 有没有办法清除线程本地存储(终止时)?
  2. 对于线程范围的对象是否有等效的“ReleaseAndDisposeAllHttpScopedObjects”?
  3. 有没有办法使已处理的对象无效(或弹出),这样即使 SM 寻找它也找不到任何对象并且必须创建一个新实例?

我希望我的问题说清楚了。这花了我几个小时的时间,但我仍然没有找到解决方法。 我很感激任何提示:>

更新: 我添加了自己的解决方案,使 StructureMap 提供的 UoW 能够处理 http 请求和quartz 作业。如果您有更好/更容易/更简单的解决方案,请告诉我。

[OR] How to define a StructureMap life cycle for UoW to be consumed by http requests and quartz jobs

I have this web application which uses SM for IoC. I am using HybridHttpOrThreadLocalScoped scope to store my nHibernate ISession objects. This works ok in a session per request fashion for my web requests.

But I also have quartz.net that schedules couple of jobs. The job uses the same unit of work to get the ISession object. In this scenario when the scheduler start the job, everything works fine at first and the job runs fine for couple of times UNTIL the job thread id gets repeated.

Imagine that when the job is scheduled it start to run in threads with ids 11, 12, 13, and then with thread id 11 again. At this point structuremap returns a session object which is already disposed and I get "System.ObjectDisposedException: Session is closed!" error.

So from what I can see, the session is kept in thread local storage and after I dispose the session at the end of my unit of work, the session object is still kept in the thread local storage. It seems that after the thread terminates its local storage is not cleared and somehow when a new thread with the same id is created, structuremap looks up the session in the old thread local storage (which is supposed to be cleared for the new thread I believe) and returns the session object which is already disposed.

Questions:

  1. Is there a way to clear the thread local storage (on termination)?
  2. Is there an equivalent of "ReleaseAndDisposeAllHttpScopedObjects" for thread-scoped objects?
  3. Is there a way to nullify (or eject) the disposed object so even if SM looks for it then it wouldn't find any and has to create a new instance?

I hope I made my question clear. This has taken couple of hours of my time and still I haven't found a way around it. I appreciate any hint :>

Update:
I added my own solution to make a UoW served by StructureMap work with both http requests and quartz jobs. Let me know if you have a better/easier/simpler solution.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

何以笙箫默 2024-09-15 01:36:51

我正在重新审视我为使 StructureMap 与每个 Http 的 UoW 和每个石英作业的 UoW 一起工作所做的工作,我决定在这里分享我的解决方案。

所以我的想法是,我想使用 StructureMap 混合作用域在存在 http 上下文时获取 UoW 的实例,并在没有 http 上下文时(例如当石英作业触发时)为每个线程获取不同的 UoW 实例。像这样:

For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<UnitOfWork>();

UoW for http 工作得很好。问题是每个线程的 UoW。

这是发生的事情。当 quratz 作业触发时,它会从线程池中提取一个线程并开始使用该线程执行作业。当工作开始时,我请求一份 UoW。 StructureMap 在本地存储下查找该线程以返回 UoW,但因为它找不到任何 UoW,所以它实例化了一个并将其保存在线程的本地存储下。我获取了 UoW,然后执行 Begin、Commit、Dispose,一切都很好。

当从之前用于触发作业(并使用了 UoW)的线程池中提取线程时,就会出现问题。在这里,当您请求 UoW 时,StructureMap 会在缓存(线程本地存储)中查找并找到 UoW 并将其返回给您。但问题是UoW被处置了!

因此,我们不能真正将每个线程的 UoW 用于quartz作业,因为线程本身没有被释放,并且它们保存旧的缓存的已释放的UoW。 基本上,线程的生命周期与quartz作业的生命周期不匹配。这就是为什么我为quartz作业创建了自己的生命周期。

首先,我创建了自己的 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"; } }
}

然后我需要创建一些上下文类,例如 HttpContext for Quartz 来保存上下文相关信息。所以我创建了 QuartzContext 类。 当一个quartz作业被触发时,该作业的JobExecutionContext应该在QuartzContext中注册。然后,将在该特定 JobExecutionContext 下创建 StructureMap 实例的实际缓存 (MainObjectCache)。这样,作业执行完成后,缓存也会消失,我们就不会有在缓存中处理 UoW 的问题。

另外,由于 _jobExecutionContext 是 ThreadStatic,当我们从 QuartzContext 请求缓存时,它会返回来自为同一线程保存的 JobExecutionContext 的缓存。因此,当多个作业同时运行时,它们的 JobExecutionContext 是单独保存的,我们将为每个正在运行的作业拥有单独的缓存。

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 源代码以获得想法。)

请分享您的想法、意见和建议:>

I was revisiting what I did to make StructureMap work with UoW per Http and UoW per quartz job and I decided to share my solution here.

So the idea was that I wanted to use StructureMap Hybrid scope to get an instance of UoW when there is a http context and also get a different instance of UoW per thread when there is no http context (like when a quartz job fires). Like this:

For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<UnitOfWork>();

The UoW for http worked fine. The problem was UoW per thread.

Here is what happens. When a quratz job fires it pulls a thread from the thread pool and starts executing the job using that thread. When the job starts I request a UoW. StructureMap looks under the local storage for that thread to return the UoW, but because it can't find any it instantiates one and saves it under thread's local storage.I get the UoW, then perfom Begin, Commit, Dispose and everything is fine.

The problem happens when a thread is pulled from thread pool which was used before to fire a job (and used a UoW). Here when you request a UoW, StructureMap looks in the cache (thread local storage) and finds a UoW and returns it to you. But the problem is the UoW is disposed!

So we cannot really use UoW per thread for quartz jobs because the threads themselves are not disposed and they hold the old cached disposed UoWs. Basically life cycle of a thread does not match the life cycle of a quartz job. That's why I created my own life cycle for a quartz job.

First I created my own http-quartz hybrid life cycle class:

public class HybridHttpQuartzLifecycle : HttpLifecycleBase<HttpContextLifecycle, QuartzLifecycle>
{
    public override string Scope { get { return "HybridHttpQuartzLifecycle"; } }
}

Then I created my QuartzLifecyle class:

public class QuartzLifecycle : ILifecycle
{

    public void EjectAll()
    {
        FindCache().DisposeAndClear();
    }

    public IObjectCache FindCache()
    {
        return QuartzContext.Cache;
    }

    public string Scope { get { return "QuartzLifecycle"; } }
}

Then I need to create some context class like HttpContext for Quartz to hold the context related info. So I created QuartzContext class. When a quartz job is fired, the JobExecutionContext for that job should be registered in QuartzContext. Then the actual cache (MainObjectCache) for StructureMap instances will be created under that specific JobExecutionContext. So this way after the job execution finishes the cache will go away too and we won't have problem of disposed UoW in cache.

Also since _jobExecutionContext is ThreadStatic, when ever we request the cache from QuartzContext, it will return the cache from the JobExecutionContext that is saved for the same thread. So when multiple jobs are running at the same time, their JobExecutionContexts are saved separately and we will have separate caches for each running job.

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

I have an abstract class called BaseJobSingleSession that other jobs derive from. This class extends the QuartzContext class. You can see that I register the JobExecutionContext when the job is fired.

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();
        }
    }
}

Finally I defined the life cycle for UoW:

For<IUnitOfWork>().LifecycleIs(new HybridHttpQuartzLifecycle()).Use<UnitOfWork>();

(For life cycle and context classes I looked into the StructureMap source code to get the idea.)

Please share your ideas, comments and suggestions : >

Spring初心 2024-09-15 01:36:51

为什么不为石英作业创建一个新会话?工作单元通常是数据库上的事务操作。我无法想象石英作业在事务上与网络请求/响应相关。创建新会话并不昂贵。这有可能吗?

Why not create a new session for the quartz jobs? A unit of work is typically a transactional operation on the db. I can't imagine the quartz jobs being transactionally related to the web request/responses. Creating new sessions is not expensive. Is this a possibility?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文