Исходный текст:Вход по гиперссылке виден.
Сводка: Даже если вы думаете, что знаете, что делаете, небезопасно хранить что-либо в 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 тоже пропал. (Конец)
|