7

[OR] 如何为 UoW 定义一个 StructureMap 生命周期以供 http 请求和石英作业使用

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

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

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

因此,从我所见,会话保存在线程本地存储中,并且在我的工作单元结束时处理会话之后,会话对象仍保存在线程本地存储中。似乎在线程终止后它的本地存储没有被清除,并且当一个具有相同 id 的新线程被创建时,结构映射在旧线程本地存储中查找会话(我相信应该为新线程清除) 并返回已经释放的会话对象。

问题:

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

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

更新: 我添加了自己的解决方案,使 StructureMap 提供的 UoW 可以同时处理 http 请求和石英作业。如果您有更好/更简单/更简单的解决方案,请告诉我。

4

2 回答 2

4

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

于 2011-07-12T22:53:31.057 回答
1

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

于 2011-05-06T00:47:28.363 回答