Оригінальний:Вхід за гіперпосиланням видно.
Зведення: Навіть якщо ви думаєте, що знаєте, що робите, неможливо зберігати щось у ThreadStatic member, CallContext або Thread Local у ASP.Net додатку, якщо значення було встановлено заздалегідь. до Page_Load (наприклад, у IHttpModule або конструкторі сторінок), але під час або після доступу.
[Оновлення: серпень 2008 Враховуючи, що чимало людей продовжують посилатися на цю статтю, я вважаю за необхідне уточнити, що ця поведінка обміну темами відбувається у дуже конкретний момент життєвого циклу сторінки, а не тоді, коли так вважається.] Моя формулювання після цитування Джеффа Ньюсома — невдало. Окрім цього, я дуже радий (і польщений) бачити посилання на цю статтю кілька разів у дизайнерських обговореннях щодо правильного використання HttpContext. Радий, що людям це корисно. ]
Існує багато плутанини щодо того, як реалізувати унікальні для користувача синглтони в ASP.Net — тобто глобальні дані є глобальними лише для одного користувача або запиту. Це не рідкість: публікувати транзакційні, безпекові чи інші «глобальні» дані в одному місці, а не вставляти їх у кожен виклик методу, оскільки loam-дані дозволяють більш чітку (і читабельну) реалізацію. Однак, якщо не бути обережним, це чудове місце, щоб вистрілити собі в ногу (або голову). Я думав, що знаю, що відбувається, але не знав.
Переважним варіантом буде одиночний варіантЗберігається у HttpContext.Current.Items, простий і безпечний, але пов'язуючи Singleton із його використанням у ASP.Net застосуванні. Якщо одиночна робота не впорається у вашому бізнес-об'єкті, це не ідеально. Навіть якщо ви обгортаєте доступ до нерухомості в if оператор
Summary: Навіть якщо ви думаєте, що знаєте, що робите, небезпечно зберігати щось у ThreadStatic member, CallContext або Thread Local Storage у ASP.Net додатку, якщо є така можливість що значення може бути встановлене до Page_Load (наприклад, у IHttpModule або конструкторі сторінок), але отримати доступ під час або після.
[Оновлення: серпень 2008 З огляду на досить велику кількість людей, які продовжують посилатися на цей пост, я вважаю за потрібне уточнити, що ця поведінка перемикання темами відбувається у дуже конкретному місці сторінки Життєвий цикл, а не коли захочеться. Моя формулювання після цитати Джефа Ньюсона було невдалим. Окрім цього, я був надзвичайно задоволений (і польщений) кількістю разів, коли бачив цей допис у дизайнерських обговореннях щодо правильного використання HttpContext. Радий, що людям це було корисно.]
Існує багато плутанини щодо того, як реалізувати одиниці, специфічні для користувача, у ASP.Net — тобто глобальні дані, які глобальні лише для одного користувача або запиту. Це не рідкісна вимога: публікація транзакцій, контексту безпеки чи інших «глобальних» даних в одному місці, замість того, щоб просувати їх через кожен виклик методу, оскільки tramp data може створити чистіша (і більш читабельна) реалізація. Однак це чудове місце, щоб нашкодити собі в ногу (або голову), якщо не бути обережним. Я думав, що знаю, що відбувається, але не знав.
Бажаний варіант — зберігати синглтони в HttpContext.Current.Items — простий і безпечний, але пов'язує одиночний тон із використанням у ASP.Net додатку. Якщо одиночник у ваших бізнес-об'єктах, це не ідеально. Навіть якщо обгорнути property-access у if-оператор
... тоді все одно потрібно звертатися до System.Web з цієї асемблерки, яка зазвичай містить більше об'єктів «webby» не в тому місці.
Альтернативи — використовувати статичний елемент [ThreadStatic], локальне сховище потоку (що фактично означає те саме) або 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 виконання. Потім, після того, як це буде поставлено в чергу, потік вводу/виводу запитує робочий потік, і потім потік вводу/виводу повертається у свій пул. [...] Тож ASP.NET доручу робочому потоку обробити запит. Він переведе його у ASP.NET виконання, так само як це робить хід/вивід при низькому навантаженні. Я завжди знав про це, але, здається, це сталося дуже рано, і мені було байдуже. Однак, здається, я помиляюся. У нас була проблема в ASP.Net додатку, коли користувач натискав на посилання після переходу на інше, і в нашому додатку був виняток null reference в одному з синглтонів (я використовував CallContext замість ThreadStatic для синглтона, але це виявилося неважливим).
Я трохи дослідив, як саме працюють ASP.Net потоки, і отримав суперечливі думки, замасковані під факти (запити є гнучкими у запитах, тоді як запити прив'язані до потоків протягом усього їхнього життя), тому я скопіював свою проблему в тестовому додатку з повільною сторінкою (яка на секунду спить) і швидкою. Я натискаю на посилання на повільну сторінку, і перед поверненням сторінки натискаю на посилання на швидку сторінку. Результат (log4net dump про те, що відбувається) просто вразив мене.
Вихід показує, що для другого запиту подія BeginRequest і конструктор сторінки в конвеєрі HttpModule запускаються на одному потоку, але page_Load в іншому. Другий потік уже мігрував HttpContext з першого потоку, але не CallContext або ThreadStatic (примітка: оскільки сам HttpContext зберігається у CallContext, це означає, що ASP.Net явно мігрує HttpContext). Скажімо ще раз:
Я завжди знав про це, але припускав, що це сталося досить рано, і мені було байдуже. Однак, здається, я помилявся. У нас виникла проблема в нашому ASP.Net додатку, коли користувач натискає одне посилання одразу після наступного, і наш додаток вибухає через виняток null reference для одного з наших синглтонів (я використовую CallContext, а не ThreadStatic для синглтона, але виявляється, це не має значення).
Я трохи дослідив, як саме ASP. Потоки мережі працюють, і вони отримали суперечливі думки — маскування під факт (запити є поточно-гнучкими в межах запиту, тоді як запити закріплені до потоку протягом усього їхнього життя), тому я відтворив свою Проблема в тестовому застосунку з повільною сторінкою (спить на секунду) і швидкою сторінкою. Я натискаю на посилання на повільну сторінку, а перед тим, як сторінка повернеться, натискаю на посилання швидкої сторінки. Результати (дамп 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 (фу), або вам доведеться розробити якусь модель провайдера для вашого Статична стійкість, яку потрібно налаштувати до того, як будь-який із цих синглтонів буде доступний. Подвійне фу.
Будь ласка, хтось скаже, що це не так.
Appendix: That log in full: [3748] ІНФО 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - BEGIN /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] ІНФО 11:10:05,679 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(now)=1835, calldata= [2720] ІНФО 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 теж зник. (Кінець)
|