Ця стаття є дзеркальною статтею машинного перекладу, будь ласка, натисніть тут, щоб перейти до оригінальної статті.

Вид: 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, простий і безпечний, але пов'язуючи 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 теж зник.
(Кінець)




Попередній:.NET/C# колекція Any() або Count(), що швидше
Наступний:Як Lazy у C# зберігає теми в безпеці
 Орендодавець| Опубліковано 30.06.2023 20:35:23 |
 Орендодавець| Опубліковано 02.07.2023 09: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