Эта статья является зеркальной статьёй машинного перевода, пожалуйста, нажмите здесь, чтобы перейти к оригиналу.

Вид: 3568|Ответ: 2

[Источник] ASP.NET разница между ThreadStatic, CallContext и HttpContext

[Скопировать ссылку]
Опубликовано 30.06.2023 20:34:10 | | |
Исходный текст:Вход по гиперссылке виден.



Сводка:
Даже если вы думаете, что знаете, что делаете, небезопасно хранить что-либо в ThreadStatic member, CallContext или Thread Local в ASP.Net приложении, если значение было заранее установлено. в Page_Load (например, в IHttpModule или конструкторе страниц), но во время или после доступа.

[Обновление: август 2008 Учитывая, что многие продолжают ссылаться на эту статью, я считаю необходимым уточнить, что это поведение обмена темами происходит в очень конкретном этапе жизненного цикла страницы, а не тогда, когда так захочет.] Моя формулировка после цитаты Джеффа Ньюсома неудачна. Кроме того, я очень рад (и польщён) видеть ссылки на эту статью несколько раз в обсуждениях дизайна по поводу правильного обращения с HttpContext. Рад, что людям это полезно. ]

Существует много путаницы относительно того, как реализовать пользовательские синглтоны в ASP.Net — то есть глобальные данные глобальны только для одного пользователя или запроса. Это не редкость: публиковать транзакционные, контекстные безопасности или другие «глобальные» данные в одном месте, а не вставлять их в каждый вызов метода, поскольку данные loam позволяют реализовать более чётко (и читаемо). Однако, если не быть осторожным, это отличное место, чтобы выстрелить себе в ногу (или голову). Я думал, что понимаю, что происходит, но не знал.

Предпочтительным вариантом будет одиночный вариантХранится в HttpContext.Current.Items, просто и безопасно, но связывая одиночный тон с его использованием в ASP.Net применении. Если Singleton не справляется с вашей бизнес-задачей, это не идеально. Даже если вы обернете доступ к собственности в if-оператор


Summary:
Даже если вы думаете, что знаете, что делаете, небезопасно хранить что-либо в ThreadStatic member, CallContext или Thread Local Storage внутри ASP.Net приложения, если такая возможность есть что значение может быть зададено до Page_Load (например, в IHttpModule или конструкторе страниц), но получить доступ во время или позже.

[Обновление: авг 2008 Учитывая довольно большое количество людей, продолжающих ссылаться на этот пост, я считаю необходимым уточнить, что такое поведение по обмену темами происходит в очень конкретном месте страницы Жизненный цикл, а не когда захочет. Моя формулировка после цитаты Джефа Ньюсона была неудачной. В стороне, меня очень рад (и польщён) количество раз, когда я видел этот пост в обсуждениях дизайна по поводу правильного обращения с HttpContext. Рад, что людям это пригодилось.]

Существует много путаницы с тем, как реализовать пользовательские синглтоны в ASP.Net — то есть глобальные данные, которые глобально доступны только одному пользователю или запросу. Это не редкость: публикация транзакций, контекста безопасности или других «глобальных» данных в одном месте, а не пропускать их через каждый вызов метода, как это может привести к более чистая (и более читаемая) реализация. Однако это отличное место, чтобы выстрелить себе в ногу (или голову), если не быть осторожным. Я думал, что понимаю, что происходит, но не знал.

Предпочтительный вариант — хранение синглтонов в HttpContext.Current.Items — простой и безопасный, но связывает этот синглтон с использованием в ASP.Net приложении. Если синглтон находится в ваших бизнес-объектах, это не идеально. Даже если вы обернете доступ к свойствам в оператор if

... тогда всё равно нужно обращаться к System.Web из этой сборки, которая обычно содержит больше 'webby' объектов не в том месте.

Альтернативы — использовать статический элемент [ThreadStatic], локальное хранилище Thread (что по сути то же самое) или CallContext.

Проблемы с [ThreadStatic] хорошо задокументированы, но, если подытожим:
Полевые инициаторы стреляют только на первой нити
Данные ThreadStatic требуют явной очистки (например, в EndRequest), потому что, хотя Thread доступен, данные ThreadStatic не будут GC, и вы можете терять ресурсы.
ThreadStatic данные полезны только внутри запроса, потому что следующий запрос может прийти по другому потоку и получить чужие данные.
Скотт Хансельман прав, что ThreadStatic плохо сочетается с ASP.Net, но не полностью объясняет почему.

Хранение в CallContext устраняет некоторые из этих проблем, так как контекст исчезает в конце запроса, и GC происходит со временем (хотя вы всё равно можете утекать ресурсы до тех пор, пока не произойдёт GC, если вы храните одноразовые вещи). Кроме того, CallContext — это способ хранения HttpContext, значит, это нормально, верно? Неуважительно, можно было бы подумать (как и я), что если ты убираешь за собой после каждого запроса, всё будет нормально:
«Если вы инициализируете переменную ThreadStatic в начале запроса и правильно обработаете объект в конце запроса, я рискну заявить, что ничего плохого не произойдёт

«А теперь,Могу ошибаться. CLR может престать размещать поток на полпути, сериализировать его стек где-то, дать ему новый стек и дать ему начать работу。 Я глубоко сомневаюсь в этом. Возможно, гиперпоток тоже усложняет задачу, но я в этом сомневаюсь. ”

Джефф Ньюсом


«Если вы инициализуете переменную ThreadStatic в начале запроса и правильно избавитесь от объекта в конце запроса, я рискну заявить, что ничего не Плохое случится. Вы даже круты между контекстами в одном AppDomain

«Возможно, я ошибаюсь. CLR потенциально может остановить управляемый поток в середине потока, сериализировать его стек где-то, дать ему новый стек и дать ему начать выполнение. Я серьёзно сомневаюсь. Полагаю, возможно, что гиперпоток тоже усложняет задачу, но я тоже сомневаюсь.»
Джеф Ньюсом
Обновление: Это вводит в заблуждение. Позже я объясню подробнее, что этот обмен темами может происходить только между BeginRequest и Page_Load, но ссылка Jef создаёт очень сильное изображение, которое я не смог сразу исправить.

Update: This was the misleading bit. I do explain further later on that this thread-swap can only happen between the BeginRequest and the Page_Load, but Jef's quote creates a very powerful image I failed to immediately correct. My bad.
В какой-то момент ASP.NET решаю, что слишком много потоков ввода-вывода, обрабатывающих другие запросы. […] Он принимает запросы только и ставит их в очередь в объекте внутренней очереди в рамках ASP.NET времени выполнения. Затем, после очереди, поток ввода-вывода запросит рабочий поток, после чего поток ввода/вывода возвращается в свой пул. […] Поэтому ASP.NET доверяю этому работнику обработку запроса. Он переведёт процесс в ASP.NET время выполнения, так же, как поток ввода-вывода при низкой нагрузке.

В какой-то момент ASP.NET решает, что слишком много потоков ввода-вывода, обрабатывающих другие запросы. [...] Он просто принимает запрос и ставит его в очередь в этом объекте внутренней очереди в рамках ASP.NET runtime. Затем, после того как это будет поставлено в очередь, поток ввода-вывода запросит рабочий поток, и затем поток ввода-вывода возвращается в свой пул. [...] Поэтому ASP.NET дадим этому рабочему потоку обработать запрос. Он переведёт его в ASP.NET время выполнения, так же, как резьба ввода/вывода при низкой нагрузке.

Я всегда знал об этом, но, кажется, это случилось очень рано, и мне было всё равно. Однако, похоже, я ошибаюсь. У нас была проблема в ASP.Net приложении, когда пользователь кликал по ссылке после перехода по другой, и в одном из синглтонов было исключение null reference (я использовал CallContext вместо ThreadStatic для синглтона, но оно оказалось неактуальным).

Я немного изучил то, как именно работают ASP.Net потоки, и получил противоречивые мнения, замаскированные под факты (запросы бывают гибкими в запросах, а запросы закрепляются на потоках в течение их жизни), поэтому я скопировал свою проблему в тестовом приложении с медленной страницей (на секунду спящей) и быстрой страницей. Я кликаю по ссылке на замедленную страницу, и перед возвращением страницы кликаю по ссылке на быструю страницу. Результат (дамп с log4net о происходящем) взорвал мой мозг.

Вывод показывает, что для второго запроса событие BeginRequest и конструктор страницы в конвейере HttpModule срабатывают на одном потоке, но page_Load в другом. Второй поток уже мигрировал HttpContext из первого, но не CallContext или ThreadStatic (примечание: поскольку сам HttpContext хранится в CallContext, это означает, что ASP.Net явно мигрирует HttpContext). Давайте повторим:


Я всегда знал об этом, но думал, что это произошло достаточно рано, и мне было всё равно. Однако, похоже, я ошибался. У нас возникла проблема в приложении ASP.Net: пользователь кликает по одной ссылке сразу после другой, и наше приложение взрывается с исключением null reference для одного из наших синглтонов (я использую CallContext, а не ThreadStatic для синглтона, но оказывается, это не имеет значения).

Я немного изучал, как именно ASP. Threading в Net работает и получил противоречивые мнения — маскирующиеся под факты (запросы в рамках запроса гибкие по потоку, а запросы закрепляются на течение всей жизни), поэтому я воспроизвел Проблема в тестовом приложении с медленной страницей (спящая на секунду) и быстрой страницей. Я кликаю по ссылке на замедленную страницу, а перед тем, как страница возвращается, нажимаю на ссылку быстрой страницы. Результаты (сброс log4net о происходящем) меня удивили.

Вывод показывает, что для второго запроса события BeginRequest в конвейере HttpModule и конструктор страниц срабатывают на одном потоке, а Page_Load срабатывает на другом. Во втором потоке HttpContext был перенесён из первого, но не CallContext или ThreadStatic (примечание: поскольку сам HttpContext хранится в CallContext, это означает, что ASP.Net явно мигрирует HttpContext поперек). Давайте сформулируем это ещё раз:

  • Переключение потоков происходит после создания IHttpHandler
  • После запуска инициализатора поля страницы и конструктора
  • После любых событий типа BeginRequest, AuthenticateRequest, AquireSessionState, используемых Global.ASA/IHttpModules.
  • В новый поток мигрируется только HttpContext



Переключатель потоков происходит после создания IHttpHandler
После запуска инициализатора полей страницы и конструктора
После любых событий типа BeginRequest, AuthenticateRequest, AquireSessionState, которые используют ваши Global.ASA / IHttpModules.
Только HttpContext мигрирует в новый поток

Это большая головная боль, потому что, судя по тому, что я видел, единственный вариант сохранения поведения класса «ThreadStatic» в ASP.Net — это использование HttpContext. Так что для ваших бизнес-объектов либо вы продолжаете использовать if(HttpContext.Current!). =null) и System.Web reference (фу), или нужно придумать какую-то модель провайдера для статической устойчивости, которую нужно настроить перед доступом к этим синглтонам. Двойная тошнота.

Пожалуйста, скажите, что это не так.

Приложение: Полный журнал:


Это большая проблема, потому что, насколько я понимаю, единственный вариант сохранения поведения в стиле 'ThreadStatic в ASP.Net — это использование HttpContext. Так что для ваших бизнес-объектов либо вы застряли с if(HttpContext.Current!=null) и System.Web reference (фу), либо вам нужно придумать какую-то модель провайдера для вашего статическая устойчивость, которую нужно настроить до того, как к любым из этих синглтонов будет доступен. Двойное фу.

Пожалуйста, кто-нибудь скажет, что это не так.

Appendix: That log in full:
[3748] ИНФО 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - START /ConcurrentRequestsDemo/SlowPage.aspx
[3748] ИНФО 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(now)=97, calldata=
[3748] ИНФО 11:10:05,249 ASP. SlowPage_aspx.. ctor() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748
[3748] ИНФО 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748
[3748] ИНФО 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - Медленное спящее на странице....

[2720] ИНФО 11:10:05,669 ASP. Global_asax. Application_BeginRequest() - BEGIN /ConcurrentRequestsDemo/FastPage.aspx
[2720] INFO 11:10:05,679 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(now)=1835, calldata=
[2720] INFO 11:10:05,679 ASP. FastPage_aspx.. ctor() - threadid=2720, threadhash=(cctor)1835, threadhash(now)=1835, calldata=2720, logicalcalldata=2720, threadstatic=2720

[3748] ИНФО 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - Медленное пробуждение страницы....
[3748] ИНФО 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748
[3748] ИНФО 11:10:06,350 ASP. Global_asax. Application_EndRequest() - threadid=3748, threadhash=97, threadhash(now)=97, calldata=3748
[3748] ИНФО 11:10:06,350 ASP. Global_asax. Application_EndRequest() - КОНЕЦ /ConcurrentRequestsDemo/SlowPage.aspx

[4748] ИНФО 11:10:06,791 ASP. FastPage_aspx. Page_Load() - threadid=2720, threadhash=(cctor)1835, threadhash(now)=1703, calldata=, logicalcalldata=, threadstatic =
[4748] ИНФО 11:10:06,791 ASP. Global_asax. Application_EndRequest() - threadid=2720, threadhash=1835, threadhash(now)=1703, calldata=
[4748] ИНФО 11:10:06,791 ASP. Global_asax. Application_EndRequest() - КОНЕЦ /ConcurrentRequestsDemo/FastPage.aspx
Главное — что происходит, когда срабатывает Page_Load FastPage. ThreadID — 4748, но ThreadID, сохранённый в HttpContext ctor, — 2720. Хеш-код для логических потоков — 1703, но хеш-код, который я храню в CTOR, — 1835. Все данные, которые я хранил в CallContext, отсутствуют (даже данные, отмеченные как ILogicalThreadAffinnative), но HttpContext всё равно присутствует. Как и следовало ожидать, мой ThreadStatic тоже исчез.

Главное — что происходит, когда срабатывает Page_Load FastPage. ThreadID — 4748, но ThreadID, который я сохранил в HttpContext в CTOR, — 2720. Хеш-код логического потока — 1703, но тот, что я сохранил в CTOR, — 1835. Все данные, которые я хранил в CallContext, исчезли (даже те, что отмечены как ILogicalThreadAffinative), но HttpContext всё ещё там. Как и следовало ожидать, мой ThreadStatic тоже пропал.
(Конец)




Предыдущий:.NET/C# collection Any() или Count(), что быстрее
Следующий:Как Lazy в C# сохраняет безопасность ниток
 Хозяин| Опубликовано 30.06.2023 20:35:23 |
 Хозяин| Опубликовано 02.07.2023 9:59:06 |
CallContext

Пространство имён: System.Runtime.Remoting.Messaging
Полностью квалифицированное имя: System.Runtime.Remoting.Messaging.CallContext
Официальное введение:Вход по гиперссылке виден.

Цель: Предоставить набор атрибутов, которые передаются вместе с путём кода выполнения, проще говоря: предоставить возможность передавать данные по пути выполнения потокового (многопоточного/однопоточного) кода.
методописаниеМожно ли использовать его в многопоточной среде
SetDataХраните заданный объект и ассоциируйте его с заданным именем.не
GetDataПолучите объект с указанным именем из System.Runtime.Remoting.Messaging.CallContextне
LogicalSetDataХраните заданный объект в контексте логического вызова и привязывайте его к заданному имени.быть
LogicalGetDataПолучить объекты с указанными именами из логического контекста вызова.быть
FreeNamedDataSlotОсвободите слоты данных с указанным именем.быть
HostContextПолучите или установите контекст хоста, связанный с текущей течью. В веб-среде он равен System.Web.HttpContext.Currentне


Отказ:
Всё программное обеспечение, программные материалы или статьи, публикуемые Code Farmer Network, предназначены исключительно для учебных и исследовательских целей; Вышеуказанный контент не должен использоваться в коммерческих или незаконных целях, иначе пользователи несут все последствия. Информация на этом сайте взята из Интернета, и споры по авторским правам не имеют отношения к этому сайту. Вы должны полностью удалить вышеуказанный контент с компьютера в течение 24 часов после загрузки. Если вам нравится программа, пожалуйста, поддержите подлинное программное обеспечение, купите регистрацию и получите лучшие подлинные услуги. Если есть нарушение, пожалуйста, свяжитесь с нами по электронной почте.

Mail To:help@itsvse.com