Вопрос

Несколько дней назад у меня возникла проблема с многопоточностью ASP.Net.Я хотел иметь одиночный объект для каждого веб-запроса.Мне это действительно нужно для моей работы.Я хотел создать экземпляр единицы работы для каждого веб-запроса, чтобы карта идентификации была действительна на протяжении всего запроса.Таким образом, я мог бы использовать IoC для прозрачного внедрения моего собственного IUnitOfWork в классы моего репозитория, и я мог бы использовать тот же экземпляр для запроса, а затем обновления моих объектов.

Поскольку я использую Unity, я по ошибке использовал PerThreadLifeTimeManager.Вскоре я понял, что модель потоков ASP.Net не поддерживает то, чего я хочу достичь.По сути, он использует пул потоков и перерабатывает потоки, а это означает, что я получаю один UnitOfWork на каждый поток!Однако мне нужна была одна единица работы на каждый веб-запрос.

Немного поиска в Google дало мне этот замечательный пост.Это было именно то, чего я хотел;за исключением части единства, которой было довольно легко достичь.

Это моя реализация PerCallContextLifeTimeManager для единства:

public class PerCallContextLifeTimeManager : LifetimeManager
{
    private const string Key = "SingletonPerCallContext";

    public override object GetValue()
    {
        return CallContext.GetData(Key);
    }

    public override void SetValue(object newValue)
    {
        CallContext.SetData(Key, newValue);
    }

    public override void RemoveValue()
    {
    }
}

И, конечно же, я использую это для регистрации своей единицы работы с помощью кода, подобного этому:

unityContainer
            .RegisterType<IUnitOfWork, MyDataContext>(
            new PerCallContextLifeTimeManager(),
            new InjectionConstructor());

Надеюсь, это сэкономит кому-то немного времени.

Это было полезно?

Решение

Отличное решение, но каждый экземпляр LifetimeManager должен использовать уникальный ключ, а не константу:

private string _key = string.Format("PerCallContextLifeTimeManager_{0}", Guid.NewGuid());

В противном случае, если у вас есть более одного объекта, зарегистрированного с помощью PerCallContextLifeTimeManager, они используют один и тот же ключ для доступа к CallContext, и вы не получите ожидаемый объект обратно.

Также стоит реализовать RemoveValue, чтобы обеспечить очистку объектов:

public override void RemoveValue()
{
     CallContext.FreeNamedDataSlot(_key);
}

Другие советы

Хотя это нормально, вызывая PerCallContextLifeTimeManager, я почти уверен, что это нет «безопасным» считается LifeTimeManager ASP.Net для каждого запроса.

Если ASP.Net выполняет замену потоков, то единственное, что в новый поток через CallContext передается текущий HttpContext — все остальное, что вы храните в CallContext, исчезнет.Это означает, что при большой нагрузке приведенный выше код может привести к непредвиденным результатам — и я думаю, что будет очень сложно выяснить, почему!

Единственный «безопасный» способ сделать это — использовать HttpContext.Current.Items или сделать что-то вроде:

public class PerCallContextOrRequestLifeTimeManager : LifetimeManager
{
    private string _key = string.Format("PerCallContextOrRequestLifeTimeManager_{0}", Guid.NewGuid());

    public override object GetValue()
    {
      if(HttpContext.Current != null)
        return GetFromHttpContext();
      else
        return GetFromCallContext();
    }

    public override void SetValue(object newValue)
    {
      if(HttpContext.Current != null)
        return SetInHttpContext();
      else
        return SetInCallContext();
    }

    public override void RemoveValue()
    {
    }
}

Очевидно, это означает использование зависимостей от System.Web :-(

Более подробную информацию об этом можно получить по адресу:

http://piers7.blogspot.com/2005/11/threadstatic-callcontext-and_02.html

Спасибо за ваш вклад,

Но предлагаемая реализация вопроса имеет два недостатка, одним из которых является серьезная ошибка, как уже заявил Стивен Роббинс в его ответ и Мика Золту в комментарии.

  1. Asp.Net не гарантирует сохранение контекста вызова для одного запроса.Под нагрузкой он может переключиться на другой, что приведет к поломке предложенной реализации.
  2. Он не обрабатывает освобождение зависимостей в конце запроса.

В настоящее время пакет Unity.Mvc Nuget предоставляет PerRequestLifetimeManager за выполнение работы.Не забудьте зарегистрировать связанный с ним UnityPerRequestHttpModule в коде начальной загрузки, в противном случае освобождение зависимостей также не будет обработано.

Использование начальной загрузки

DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));

или в web.config system.webServer/modules

<add name="UnityPerRequestHttpModule" type="Microsoft.Practices.Unity.Mvc.UnityPerRequestHttpModule, Microsoft.Practices.Unity.Mvc" preCondition="managedHandler" />

Похоже, что его текущая реализация также подходит для веб-форм.И это даже не зависит от MVC.К сожалению, его сборка делает это из-за наличия в нем некоторых других классов.

Остерегайтесь: если вы используете какой-либо собственный http-модуль, используя разрешенные зависимости, они могут уже быть расположены в модуле. EndRequest.Это зависит от порядка выполнения модуля.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top