Este artículo es un artículo espejo de traducción automática, por favor haga clic aquí para saltar al artículo original.

Vista: 3568|Respuesta: 2

[Fuente] ASP.NET la diferencia entre ThreadStatic, CallContext y HttpContext

[Copiar enlace]
Publicado en 30/6/2023 20:34:10 | | |
Texto original en:El inicio de sesión del hipervínculo es visible.



Resumen:
Aunque creas que sabes lo que haces, no es seguro guardar nada en un ThreadStatic member, CallContext o Thread Local Store en una aplicación ASP.Net si el valor se había establecido previamente. para Page_Load (por ejemplo, en IHttpModule o constructor de páginas), pero durante o después del acceso.

[Actualización: agosto de 2008 Dado que bastantes personas siguen enlazando este artículo, creo que es necesario aclarar que este comportamiento de intercambio de hilos ocurre en un punto muy específico del ciclo de vida de la página, y no cuando lo sienta.] Mi forma de expresarme tras citar a Jeff Newsom es desafortunada. Aparte de eso, me alegra mucho (y me halaga) ver referencias a este artículo en varias ocasiones en discusiones de diseño sobre el manejo adecuado de HttpContext. Me alegra que a la gente le resulte útil. ]

Hay mucha confusión sobre cómo implementar singletons específicos de usuario en ASP.Net es decir, los datos globales son globales solo para un usuario o solicitud. Este no es un requisito poco común: publicar datos transaccionales, de contexto de seguridad u otros datos "globales" en un solo lugar, en lugar de enviarlos a cada llamada al método, porque los datos de longitud permiten una implementación más clara (y legible). Sin embargo, si no tienes cuidado, este es un buen lugar para dispararte en el pie (o en la cabeza). Pensé que sabía lo que pasaba, pero no era así.

La opción preferida será la monotonaAlmacenado en HttpContext.Current.Items, sencillo y seguro, pero relacionando el singleton en cuestión con su uso en la ASP.Net solicitud. Si el singleton falla en tu objeto de negocio, entonces esto no es lo ideal. Incluso si envuelves el acceso a propiedades en una sentencia if


Summary:
Aunque creas que sabes lo que haces, no es seguro almacenar nada en un miembro ThreadStatic, CallContext o Thread Local Storage dentro de una aplicación ASP.Net, si es posible que el valor puede configurarse antes de Page_Load (por ejemplo, en IHttpModule o constructor de páginas) pero accedido durante o después.

[Actualización: agosto de 2008 Dado que el número bastante grande de personas sigue enlazando esta publicación, siento la necesidad de aclarar que este comportamiento de intercambio de hilos ocurre en un punto muy específico de la página Ciclo de vida y no cuando se sienta así. Mi forma de redactarme después de la cita de Jef Newson fue desafortunada. Aparte de eso, me ha agradecido enormemente (y me ha halagado) la cantidad de veces que he visto esta publicación citada en discusiones de diseño sobre cómo tratar adecuadamente HttpContext. Me alegro de que la gente lo haya encontrado útil.]

Hay mucha confusión sobre cómo implementar singletons específicos de usuario en ASP.Net es decir, datos globales que solo son globales para un usuario o solicitud. No es un requisito poco común: publicar Transactions, contexto de seguridad u otros datos 'globales' en un solo lugar, en lugar de enviarlos a través de cada llamada de método como pueden hacer que los datos tramp una implementación más limpia (y legible). Sin embargo, es un buen lugar para dispararte en el pie (o en la cabeza) si no tienes cuidado. Pensé que sabía lo que pasaba, pero no era así.

La opción preferida, almacenar tus singletons en HttpContext.Current.Items, es sencilla y segura, pero vincula el singleton en cuestión a usarse dentro de una aplicación ASP.Net. Si el singleton está bajo en tus objetos de negocio, esto no es lo ideal. Incluso si envuelve el property-access en una instrucción if

... entonces aún tienes que referenciar System.Web desde ese ensamblaje, que suele acorstar más objetos 'webby' en el lugar equivocado.

Las alternativas son usar un miembro estático [ThreadStatic], almacenamiento local Thread (que equivale prácticamente a lo mismo) o CallContext.

Los problemas con [ThreadStatic] están bien documentados, pero para resumir:
Los iniciadores de campo solo disparan en el primer hilo
Los datos de ThreadStatic necesitan una limpieza explícita (por ejemplo, en EndRequest), porque aunque el Thread es accesible, los datos de ThreadStatic no serán GC, así que podrías estar perdiendo recursos.
Los datos ThreadStatic solo son válidos dentro de una petición, porque la siguiente petición puede llegar en otro hilo y obtener los datos de otra persona.
Scott Hanselman acierta, ThreadStatic no funciona bien con ASP.Net, pero no explica del todo por qué.

El almacenamiento en CallContext alivia algunos de estos problemas, ya que el contexto desaparece al final de la petición y la GC ocurrirá eventualmente (aunque aún puedes filtrar recursos hasta que ocurra la GC si estás almacenando desechables). Además, CallContext es como se almacena HttpContext, así que debe estar bien, ¿no? De todos modos, pensarías (como yo) que, siempre que limpiaras después de cada petición, todo estaría bien:
"Si inicializas la variable ThreadStatic al principio de la solicitud y gestionas correctamente el objeto referenciado al final de la petición, me arriesgaría a decir que no pasará nada malo

"Ahora,Podría estar equivocado. CLR puede dejar de alojar un hilo a mitad de camino, serializar su pila en algún lugar, darle una nueva pila y dejar que empiece a ejecutarse。 Lo dudo mucho. Supongo que es concebible que el hyperthreading también lo complique las cosas, pero también lo dudo. ”

Jeff Newsom


"Si inicializas una variable ThreadStatic al principio de una petición y eliminas correctamente el objeto referenciado al final de la petición, me arriesgaré y afirmaré que nada Lo malo pasará. Incluso eres genial entre contextos dentro del mismo AppDomain

"Ahora, podría estar equivocado en esto. El clr podría potencialmente detener un hilo gestionado a mitad de secuencia, serializar su pila en algún lugar, darle una nueva pila y dejar que empiece a ejecutarse. Lo dudo mucho. Supongo que es concebible que el hyperthreading también lo complique las cosas, pero también lo dudo."
Jef Newsom
Actualización: Esto es engañoso. Explicaré más adelante que este cambio de hilo solo puede ocurrir entre BeginRequest y Page_Load, pero la referencia de Jef crea una imagen muy potente que no corrigí de inmediato.

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.
Así que, en algún momento, ASP.NET decido que hay demasiados hilos de E/S procesando otras solicitudes. […] Solo acepta peticiones y las pone en cola en un objeto de cola interno dentro de ASP.NET tiempo de ejecución. Luego, tras la cola, el hilo de E/S solicitará un hilo de trabajo, y el hilo de E/S volverá a su pool. […] Por lo tanto, ASP.NET dejará que ese trabajador gestione la solicitud. Lo llevará a ASP.NET tiempo de ejecución, igual que haría el hilo de E/S a baja carga.

Así que en algún momento ASP.NET decide que hay demasiados hilos de E/S procesando otras solicitudes. [...] Simplemente toma la petición y la pone en cola en este objeto interno dentro del tiempo de ejecución ASP.NET. Luego, una vez que eso esté en cola, el hilo de E/S pedirá un hilo de trabajo, y el hilo de E/S se devolverá a su pool. [...] Así que ASP.NET hará que ese hilo de trabajador procese la solicitud. Lo llevará al tiempo de ejecución ASP.NET, igual que el hilo de E/S lo haría bajo baja carga.

Siempre lo supe, pero creo que ocurrió muy pronto y no me importó. Sin embargo, parece que estoy equivocado. Tuvimos un problema en ASP.Net app donde un usuario hacía clic en un enlace tras hacer clic en otro, y nuestra app tenía una excepción de referencia nula en uno de los singletons (usé CallContext en lugar de ThreadStatic para el singleton, pero resultó ser irrelevante).

Investigué un poco sobre cómo funcionan exactamente ASP.Net hilos y encontré opiniones contradictorias disfrazadas de hechos (las peticiones son ágiles en los hilos, mientras que las peticiones están ancladas en hilos durante su vida), así que copié mi problema en una aplicación de prueba con una página lenta (dormida un segundo) y una página rápida. Yo hago clic en el enlace a la página lenta y, antes de que la página vuelva, hago clic en el enlace a la página rápida. El resultado (un volcado de log4net de lo que está pasando) me dejó alucinado.

La salida muestra que para la segunda petición, el evento BeginRequest y el constructor de página en la tubería HttpModule se activan en un hilo, pero page_Load en otro. El segundo hilo ya ha migrado el HttpContext del primer hilo, pero no el CallContext ni el ThreadStatic (nota: dado que el propio HttpContext se almacena en el CallContext, esto significa que ASP.Net está migrando explícitamente el HttpContext). Repitamos:


Siempre supe esto, pero asumí que ocurrió lo suficientemente pronto como para que no me importara. Sin embargo, parece que me equivoqué. Hemos tenido un problema en nuestra app de ASP.Net donde el usuario hace clic en un enlace justo después de otro, y nuestra app se rompe con una excepción de referencia nula para uno de nuestros singletons (yo uso CallContext no ThreadStatic para el singleton, pero resulta que no importa).

He investigado un poco sobre cómo se utiliza exactamente el ASP. El threading de red funciona, y genera opiniones contradictorias—disfrazadas de hechos (las solicitudes son ágiles en hilos dentro de una petición frente a las que se fijan en un hilo durante toda su vida), así que replicé mi Problema en una aplicación de prueba con una página lenta (duerme un segundo) y una página rápida. Hago clic en el enlace de la página lenta y, antes de que vuelva la página, hago clic en el enlace de la página rápida. Los resultados (un volcado de log4net de lo que está pasando) me sorprendieron.

Lo que muestra la salida es que, para la segunda petición, los eventos BeginRequest en la tubería HttpModule y el constructor de páginas se activan en un hilo, pero el Page_Load se activa en otro. El segundo hilo ha tenido el HttpContext migrado del primero, pero no el CallContext ni el ThreadStatic (Nota: dado que HttpContext se almacena en CallContext, esto significa ASP.Net es migrando explícitamente el HttpContext a través de ella). Vamos a dejarlo claro otra vez:

  • El cambio de hilos ocurre después de que se crea el IHttpHandler
  • Tras el inicializador de campos y la ejecución del constructor de la página
  • Después de cualquier evento tipo BeginRequest, AuthenticateRequest, AquireSessionState que estén siendo utilizados por Global.ASA/IHttpModules.
  • Solo el HttpContext se migra al nuevo hilo



El cambio de hilo ocurre después de que se ha creado el IHttpHandler
Tras los inicializadores de campo y la ejecución del constructor de la página
Después de cualquier evento tipo BeginRequest, AuthenticateRequest o AquireSessionState que estén utilizando tus módulos Global.ASA / IHttp.
Solo el HttpContext migra al nuevo hilo

Esto es un auténtico fastidio porque, por lo que he visto, significa que la única opción de persistencia para el comportamiento de la clase "ThreadStatic" en ASP.Net es usar HttpContext. Así que para tus objetos de negocio, sigues usando if(HttpContext.Current!). =null) y referencia System.Web (qué asco), o tienes que idear algún tipo de modelo proveedor para persistencia estática, que hay que configurar antes de acceder a estos singletons. Doble náusea.

Por favor, dime que no es así.

Apéndice: Registro completo:


Esto es un gran incordio, porque por lo que veo significa que la única opción de persistencia para un comportamiento tipo 'ThreadStatic' en ASP.Net es usar HttpContext. Así que para tus objetos de negocio, o te quedas atascado con el if(HttpContext.Current!=null) y la referencia System.Web (qué asco), o tienes que idear algún tipo de modelo de proveedor para tu persistencia estática, que necesitará configurarse antes de que se acceda a cualquiera de estos singletons. Doble asco.

Por favor, que alguien diga que no es así.

Appendix: That log in full:
[3748] INFO 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - INICIO /ConcurrentRequestsDemo/SlowPage.aspx
[3748] INFO 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(now)=97, calldata=
[3748] INFO 11:10:05,249 ASP. SlowPage_aspx... ctor() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748
[3748] INFO 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748
[3748] INFO 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - Pausa lenta de las páginas....

[2720] INFO 11:10:05,669 ASP. Global_asax. Application_BeginRequest() - INICIO /ConcurrentRequestsDemo/FastPage.aspx
[2720] INFO 11:10:05,679 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(ahora)=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] INFO 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - Página lenta despertándose....
[3748] INFO 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748
[3748] INFO 11:10:06,350 ASP. Global_asax. Application_EndRequest() - threadid=3748, threadhash=97, threadhash(ahora)=97, calldata=3748
[3748] INFO 11:10:06,350 ASP. Global_asax. Application_EndRequest() - FIN /ConcurrentRequestsDemo/SlowPage.aspx

[4748] INFO 11:10:06,791 ASP. FastPage_aspx. Page_Load() - threadid=2720, threadhash=(cctor)1835, threadhash(ahora)=1703, calldata=, logicalcalldata=, threadstatic=
[4748] INFO 11:10:06,791 ASP. Global_asax. Application_EndRequest() - threadid=2720, threadhash=1835, threadhash(ahora)=1703, calldata=
[4748] INFO 11:10:06,791 ASP. Global_asax. Application_EndRequest() - FIN /ConcurrentRequestsDemo/FastPage.aspx
La clave es lo que ocurre cuando se activa el Page_Load de FastPage. El ThreadID es 4748, pero el ThreadID que guardé en el HttpContext de ctor es 2720. El código hash para hilos lógicos es 1703, pero el código hash que guardo en el ctor es 1835. Todos los datos que almacené en el CallContext faltan (incluso los datos marcados como ILogicalThreadAffinnative), pero el HttpContext sigue ahí. Como era de esperar, mi ThreadStatic también ha desaparecido.

La clave es qué ocurre cuando se activa el Page_Load de FastPage. El ThreadID es 4748, pero el ThreadID que guardé en HttpContext en el ctor es 2720. El código hash del hilo lógico es 1703, pero el que guardé en el ctor es 1835. Todos los datos que guardé en el CallContext han desaparecido (incluso los marcados como ILogicalThreadAffinative), pero HttpContext sigue ahí. Como era de esperar, mi ThreadStatic también ha desaparecido.
(Fin)




Anterior:.NET/C# colección Any() o Count(), que es más rápida
Próximo:Cómo es perezoso en C# que mantiene los hilos seguros
 Propietario| Publicado en 30/6/2023 20:35:23 |
 Propietario| Publicado en 2/7/2023 9:59:06 |
CallContext

Espacio de nombres: System.Runtime.Remoting.Messaging
Nombre totalmente cualificado: System.Runtime.Remoting.Messaging.CallContext

Propósito: Proporcionar un conjunto de atributos que se transmiten junto con la ruta del código de ejecución, en pocas palabras: proporcionar la capacidad de pasar datos en la ruta de ejecución de código con hilos (multihilo/single-threaded).
métododescripciónSi puede usarse en un entorno multihilo
SetDataAlmacena un objeto dado y asócialo con un nombre especificado.no
GetDataRecuperar el objeto con el nombre especificado desde System.Runtime.Remoting.Messaging.CallContextno
LogicalSetDataAlmacena un objeto dado en el contexto de una llamada lógica y asócialo con un nombre especificado.ser
LogicalGetDataRecuperar objetos con nombres especificados del contexto de llamada lógica.ser
RanuraDe Datos LibreNombradosVacía los espacios de datos con el nombre especificado.ser
HostContextObtén o establece el contexto del host asociado al hilo actual. En el entorno web, es igual a System.Web.HttpContext.Currentno


Renuncia:
Todo el software, materiales de programación o artículos publicados por Code Farmer Network son únicamente para fines de aprendizaje e investigación; El contenido anterior no se utilizará con fines comerciales o ilegales; de lo contrario, los usuarios asumirán todas las consecuencias. La información de este sitio proviene de Internet, y las disputas de derechos de autor no tienen nada que ver con este sitio. Debes eliminar completamente el contenido anterior de tu ordenador en un plazo de 24 horas desde la descarga. Si te gusta el programa, por favor apoya el software genuino, compra el registro y obtén mejores servicios genuinos. Si hay alguna infracción, por favor contáctanos por correo electrónico.

Mail To:help@itsvse.com