StructureMap возвращает удаленный объект сеанса nHibenrate из локальной области видимости потока

[ИЛИ] Как определить жизненный цикл StructureMap для UoW, который будет использоваться HTTP-запросами и кварцевыми заданиями

У меня есть это веб-приложение, которое использует SM для IoC. Я использую область HybridHttpOrThreadLocalScoped для хранения моих объектов nHibernate ISession. Это нормально работает в режиме сеанса для моих веб-запросов.

Но у меня также есть quartz.net, который планирует пару вакансий. Задание использует ту же единицу работы для получения объекта ISession. В этом сценарии, когда планировщик запускает задание, сначала все работает нормально, и задание выполняется нормально несколько раз, ДО тех пор, пока идентификатор потока задания не будет повторяться.

Представьте, что когда задание запланировано, оно начинает выполняться в потоках с идентификаторами 11, 12, 13, а затем снова с идентификатором потока 11. В этот момент карта структуры возвращает объект сеанса, который уже удален, и я получаю «System.ObjectDisposedException: сеанс закрыт!» ошибка.

Итак, из того, что я вижу, сеанс хранится в локальном хранилище потока, и после того, как я удаляю сеанс в конце моей единицы работы, объект сеанса все еще сохраняется в локальном хранилище потока. Похоже, что после завершения потока его локальное хранилище не очищается, и каким-то образом, когда создается новый поток с тем же идентификатором, карта структуры просматривает сеанс в локальном хранилище старого потока (который, как предполагается, очищен для нового потока, как я полагаю) и возвращает объект сеанса, который уже удален.

Вопросы:

  1. Есть ли способ очистить локальное хранилище потока (при завершении)?
  2. Есть ли эквивалент ReleaseAndDisposeAllHttpScopedObjects для объектов с поточной областью?
  3. Есть ли способ аннулировать (или извлечь) удаленный объект, чтобы даже если SM будет его искать, он не найдет его и должен создать новый экземпляр?

Надеюсь, я прояснил свой вопрос. Это заняло пару часов моего времени, но я все еще не нашел способа обойти это. Я ценю любой намек:>

Обновление: я добавил собственное решение, чтобы UoW, обслуживаемый StructureMap, работал как с HTTP-запросами, так и с кварцевыми заданиями. Дайте мне знать, если у вас есть лучшее / простое / простое решение.


person kaptan    schedule 03.07.2010    source источник
comment
Вы управляете своими кварцевыми IJobs с помощью StructureMap?   -  person Mauricio Scheffer    schedule 04.07.2010
comment
@Mauricio: Я использую StructureMap в своем приложении. Я не совсем понимаю, что вы имеете в виду, говоря об управлении кварцевыми заданиями с помощью StructeMap: ›   -  person kaptan    schedule 13.07.2011
comment
Управляются ли ваши экземпляры Quartz IJob с помощью StructureMap? Другими словами: вы регистрируете свои вакансии в контейнере?   -  person Mauricio Scheffer    schedule 13.07.2011
comment
@Mauricio: Нет, я не регистрирую свои вакансии в контейнере StructureMap. Я использую StructureMap, чтобы получить UoW для каждой запущенной работы.   -  person kaptan    schedule 13.07.2011
comment
Я бы позволил контейнеру управлять заданиями ... это, вероятно, упростит задачу.   -  person Mauricio Scheffer    schedule 13.07.2011


Ответы (2)


Я пересматривал то, что я сделал, чтобы заставить StructureMap работать с UoW на Http и UoW на задание кварца, и я решил поделиться своим решением здесь.

Итак, идея заключалась в том, что я хотел использовать гибридную область StructureMap, чтобы получить экземпляр UoW при наличии контекста http, а также получить другой экземпляр UoW для каждого потока, когда нет контекста http (например, когда срабатывает задание кварца). Нравится:

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

UoW для http работал нормально. Проблема заключалась в 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"; } }
}

Затем мне нужно создать некоторый контекстный класс, например HttpContext для Quartz, для хранения информации, связанной с контекстом. Итак, я создал класс QuartzContext. Когда запускается кварцевое задание, JobExecutionContext для этого задания должен быть зарегистрирован в QuartzContext. Затем фактический кеш (MainObjectCache) для экземпляров StructureMap будет создан под этим конкретным JobExecutionContext. Таким образом, после завершения выполнения задания кеш также исчезнет, ​​и у нас не будет проблем с удалением 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, чтобы понять идею.)

Поделитесь своими идеями, комментариями и предложениями:>

person kaptan    schedule 12.07.2011

Почему бы не создать новую сессию для кварцевых заданий? Единица работы - это обычно транзакционная операция над базой данных. Я не могу представить, чтобы кварцевые задания были транзакционно связаны с веб-запросом / ответами. Создание новых сеансов не дорого. Это возможность?

person rcravens    schedule 06.05.2011
comment
да. Я хочу создать новую сессию для каждого кварцевого задания. Но поскольку я использую идею UoW, я хочу, чтобы она была единообразной в моем приложении. Поэтому я не хочу создавать сеансы непосредственно для кварцевых заданий. Я хочу создать экземпляр UoW. Но в то же время я хочу использовать StructureMap для получения экземпляра UoW. Вот почему я закончил определение гибридного жизненного цикла в StructeMap для моего UoW, чтобы он возвращал соответствующий UoW для каждого HTTP-запроса или каждого выполнения кварцевого задания. - person kaptan; 13.07.2011
comment
@kaptan: Я согласен с @rcravens. Я получил экземпляр ISessionFactory (ObjectFactory.GetInstance ‹ISessionFactory› ()) и открыл новый сеанс, когда сработает моя работа. - person LeftyX; 13.07.2011