Original:O login do hiperlink está visível.
Resumo: Mesmo que você ache que sabe o que está fazendo, não é seguro armazenar nada em um ThreadStatic member, CallContext ou Thread Local Store em uma aplicação ASP.Net se o valor puder ter sido definido antes. para Page_Load (por exemplo, no IHttpModule ou construtor de página), mas durante ou após o acesso.
[Atualização: agosto de 2008 Considerando que várias pessoas continuam a linkar para este artigo, acho necessário esclarecer que esse comportamento de troca de tópicos ocorre em um ponto muito específico do ciclo de vida da página, e não quando parece que sim.] Minha forma de se expressar após citar Jeff Newsom é lamentável. Fora isso, fico muito feliz (e lisonjeado) em ver referências a este artigo várias vezes em discussões de design sobre o tratamento adequado do HttpContext. Fico feliz que as pessoas achem útil. ]
Há muita confusão sobre como implementar singletons específicos de usuário em ASP.Net – ou seja, dados globais são globais para apenas um usuário ou requisição. Esse não é um requisito incomum: publicar dados transacionais, de contexto de segurança ou outros dados "globais" em um só lugar, em vez de enviá-los para cada chamada de método, porque os dados de isolamento permitem uma implementação mais clara (e legível). No entanto, se você não tomar cuidado, este é um ótimo lugar para se prejudicar (ou se machucar). Achei que sabia o que estava acontecendo, mas não sabia.
A opção preferida será singletonArmazenado em HttpContext.Current.Items, simples e seguro, mas relacionando o singleton em questão ao seu uso na aplicação ASP.Net. Se o singleton falhar no seu objeto de negócio, então isso não é o ideal. Mesmo que você envolva o acesso à propriedade em uma instrução if
Summary: Mesmo que você ache que sabe o que está fazendo, não é seguro armazenar nada em um membro ThreadStatic, CallContext ou Thread Local Storage dentro de uma aplicação ASP.Net, se houver essa possibilidade que o valor pode ser configurado antes de Page_Load (por exemplo, no IHttpModule ou construtor de página), mas acessado durante ou depois.
[Atualização: ago de 2008 Considerando o número relativamente grande de pessoas continuando a linkar para este post, sinto a necessidade de esclarecer que esse comportamento de troca de tópicos acontece em um ponto muito específico da página ciclo de vida e não quando você quiser. Minha forma de se expressar após a citação de Jef Newson foi infeliz. Deixando isso de lado, fiquei imensamente gratificado (e lisonjeado) com o número de vezes que vi este post citado em discussões de design sobre como lidar adequadamente com o HttpContext. Fico feliz que as pessoas tenham achado útil.]
Há muita confusão sobre como implementar singletons específicos de usuário em ASP.Net – ou seja, dados globais que são globais apenas para um usuário ou requisição. Esse não é um requisito incomum: publicar Transações, contexto de segurança ou outros dados 'globais' em um só lugar, em vez de enviá-los por todas as chamadas de método, pois os dados tramp podem gerar um implementação mais limpa (e mais legível). No entanto, é um ótimo lugar para se dar um tiro no pé (ou na cabeça) se não tomar cuidado. Achei que sabia o que estava acontecendo, mas não sabia.
A opção preferida, armazenar seus singletons em HttpContext.Current.Items, é simples e segura, mas vincula o singleton em questão a ser usado em uma aplicação ASP.Net. Se o singleton está baixo nos seus objetos de negócio, isso não é o ideal. Mesmo que você envolva o property-access em uma instrução if
... aí você ainda precisa referenciar o System.Web a partir desse montador, que tende a encorajar mais objetos 'webby' no lugar errado.
As alternativas são usar um membro estático [ThreadStatic], armazenamento local Thread (que basicamente equivale à mesma coisa) ou CallContext.
Os problemas com [ThreadStatic] são bem documentados, mas para resumir: Iniciadores de campo disparam apenas no primeiro fio Os dados do ThreadStatic precisam de limpeza explícita (por exemplo, no EndRequest), porque, embora o Thread seja acessível, os dados do ThreadStatic não serão GC, então você pode estar vazando recursos. Dados ThreadStatic só são úteis dentro de uma requisição, porque a próxima solicitação pode chegar em outro thread e obter os dados de outra pessoa. Scott Hanselman acerta, que ThreadStatic não funciona bem com ASP.Net, mas não explica totalmente o porquê.
O armazenamento no CallContext alivia alguns desses problemas, já que o contexto desaparece ao final da solicitação e o GC ocorrerá eventualmente (embora você ainda possa vazar recursos até que o GC aconteça se você está armazenando descartáveis). Além disso, o CallContext é como o HttpContext é armazenado, então deve estar ok, certo? De qualquer forma, você pensaria (como eu pensei) que, desde que você limpasse tudo no final de cada pedido, tudo ficaria bem: "Se você inicializar a variável ThreadStatic no início da solicitação e lidar corretamente com o objeto referenciado no final da solicitação, eu arriscaria alegar que nada de ruim acontecerá
"Agora,Posso estar errado. O CLR pode parar de hospedar um thread no meio do caminho, serializar sua pilha em algum lugar, dar uma nova pilha e deixar que ele comece a executar。 Duvido muito disso. Acho que é possível que o hyperthreading também dificulte as coisas, mas também duvido. ”
Jeff Newsom
"Se você inicializar uma variável ThreadStatic no início de uma solicitação e descartar corretamente o objeto referenciado no final da solicitação, vou arriscar e afirmar que nada Coisas ruins vão acontecer. Você é até legal entre contextos no mesmo AppDomain
"Agora, posso estar errado nisso. O clr poderia potencialmente parar uma thread gerenciada no meio do fluxo, serializar sua pilha em algum lugar, dar uma nova stack e deixá-la começar a executar. Duvido muito. Suponho que seja concebível que o hyperthreading também dificulte as coisas, mas também duvido disso." Jef Newsom Atualização: Isso é enganoso. Vou explicar mais adiante que essa troca de tópicos só pode acontecer entre BeginRequest e Page_Load, mas a referência do Jef cria uma imagem muito poderosa que eu não consegui corrigir imediatamente.
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. Então, em algum momento, ASP.NET decidir que há muitas threads de I/O processando outras solicitações. […] Ele só aceita solicitações e as coloca em um objeto interno dentro ASP.NET runtime. Depois, após a fila, a thread de I/O solicitará uma thread de trabalho, e então a thread de I/O retornará ao seu pool. […] Portanto, ASP.NET deixará esse trabalhador cuidar do pedido. Ele vai trazer para ASP.NET tempo de execução, assim como a thread de I/O faria em baixa carga.
Então, em algum momento, ASP.NET decide que há muitas threads de I/O processando outras solicitações. [...] Ele simplesmente pega a solicitação e a coloca nesse objeto interno dentro do tempo de execução ASP.NET. Depois, depois que isso for enfileirado, a thread de I/O pedirá uma thread de trabalho, e a thread de I/O será retornada ao seu pool. [...] Então ASP.NET fará com que esse worker thread processe a solicitação. Ele vai levar para o tempo de execução ASP.NET, assim como a thread de I/O faria sob baixa carga. Eu sempre soube disso, mas acho que aconteceu muito cedo e eu não me importei. No entanto, parece que estou errado. Tivemos um problema em ASP.Net app onde um usuário clicava em um link depois de clicar em outro, e nosso app tinha uma exceção de referência nula em um dos singletons (usei CallContext em vez de ThreadStatic para o singleton, mas acabou sendo irrelevante).
Pesquisei exatamente como funcionam ASP.Net threads e recebi opiniões conflitantes disfarçadas de fatos (as solicitações são ágeis em threads, enquanto as solicitações são ancoradas em threads durante toda a vida útil), então copiei meu problema em um aplicativo de teste com uma página lenta (dormindo por um segundo) e uma página rápida. Clico no link para a página lenta e, antes que a página volte, clico no link para a página rápida. O resultado (dump do log4net do que está acontecendo) me deixou impressionado.
A saída mostra que, para a segunda solicitação, o evento BeginRequest e o construtor de página no pipeline HttpModule disparam em um thread, mas page_Load em outro. O segundo thread já migrou o HttpContext do primeiro thread, mas não o CallContext ou o ThreadStatic (nota: como o próprio HttpContext é armazenado no CallContext, isso significa que ASP.Net está migrando explicitamente o HttpContext). Vamos repetir:
Sempre soube disso, mas presumi que aconteceu cedo o suficiente para não me importar. No entanto, parece que eu estava errado. Estamos tendo um problema no nosso app ASP.Net onde o usuário clica em um link logo após clicar em outro, e nosso app explode com uma exceção de referência nula para um dos nossos singletons (estou usando CallContext, não ThreadStatic para o singleton, mas no fim das contas não importa).
Pesquisei um pouco sobre como exatamente se utiliza o ASP. O threading da internet funciona, e gera opiniões conflitantes — disfarçadas de fato (os pedidos são ágeis em threads dentro de um pedido versus os pedidos são fixados em um thread durante toda a vida deles), então eu replicei meu Problema em uma aplicação de teste com uma página lenta (dorme por um segundo) e uma página rápida. Clico no link da página lenta e, antes da página voltar, cliquei no link da página rápida. Os resultados (um resumo do que está acontecendo) me surpreenderam.
O que o resultado mostra é que - para a segunda solicitação - os eventos BeginRequest no pipeline HttpModule e o construtor de página disparam em uma thread, mas a Page_Load dispara em outra. O segundo thread teve o HttpContext migrado do primeiro, mas não o CallContext ou o ThreadStatic (Nota: como o HttpContext é armazenado no CallContext, isso significa que ASP.Net é migrando explicitamente o HttpContext para o outro lado). Vamos deixar claro isso novamente:
- A troca de threads acontece após a criação do IHttpHandler
- Após a execução do inicializador de campos e do construtor da página
- Após quaisquer eventos do tipo BeginRequest, AuthenticateRequest, AquireSessionState que estejam sendo usados pelos Global.ASA/IHttpModules.
- Apenas o HttpContext é migrado para o novo tópico
A troca de thread ocorre após a criação do IHttpHandler Após a execução dos inicializadores de campo e do construtor da página Após qualquer evento do tipo BeginRequest, AuthenticateRequest, AquireSessionState que seus Global.ASA / IHttpModules estejam usando. Apenas o HttpContext migra para o novo tópico Isso é um grande incômodo porque, pelo que vi, significa que a única opção de persistência para o comportamento da classe "ThreadStatic" em ASP.Net é usar HttpContext. Então, para seus objetos de negócio, você continua usando if(HttpContext.Current!). =null) e referência System.Web (que nojo), ou você precisa criar algum tipo de modelo de provedor para persistência estática, que precisa ser configurado antes de acessar esses singletons. Náusea dupla.
Por favor, diga que não é o caso.
Apêndice: Registro completo:
Isso é um grande saco, porque pelo que vejo, significa que a única opção de persistência para comportamento 'ThreadStatic' em ASP.Net é usar HttpContext. Então, para seus objetos de negócio, ou você fica preso ao if(HttpContext.Current!=null) e à referência System.Web (que nojo), ou precisa criar algum tipo de modelo de provedor para seu persistência estática, que precisará ser configurada antes do momento em que qualquer um desses singletons seja acessado. Duplo nojo.
Por favor, alguém diga que não é verdade.
Appendix: That log in full: [3748] INFO 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - INÍCIO /ConcurrentRequestsDemo/SlowPage.aspx [3748] INFO 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(agora)=97, calldata= [3748] INFORMAÇÕES 11:10:05,249 ASP. SlowPage_aspx.. ctor() - threadid=3748, threadhash=(cctor)97, threadhash(agora)=97, calldata=3748, logicalcalldata=3748 [3748] INFO 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(agora)=97, calldata=3748, logicalcalldata=3748 [3748] INFO 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - Página de sono lenta....
[2720] INFO 11:10:05,669 ASP. Global_asax. Application_BeginRequest() - INÍCIO /ConcurrentRequestsDemo/FastPage.aspx [2720] INFO 11:10:05,679 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(agora)=1835, calldata= [2720] INFO 11:10:05,679 ASP. FastPage_aspx.. ctor() - threadid=2720, threadhash=(cctor)1835, threadhash(agora)=1835, calldata=2720, logicalcalldata=2720, threadstatic=2720
[3748] INFORMAÇÕES 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - Página lenta acordando.... [3748] INFORMAÇÕES 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(agora)=97, calldata=3748, logicalcalldata=3748 [3748] INFORMAÇÕES 11:10:06,350 ASP. Global_asax. Application_EndRequest() - threadid=3748, threadhash=97, threadhash(agora)=97, calldata=3748 [3748] INFORMAÇÕES 11:10:06,350 ASP. Global_asax. Application_EndRequest() - FIM /ConcurrentRequestsDemo/SlowPage.aspx
[4748] INFO 11:10:06,791 ASP. FastPage_aspx. Page_Load() - threadid=2720, threadhash=(cctor)1835, threadhash(agora)=1703, calldata=, logicalcalldata=, threadstatic= [4748] INFO 11:10:06,791 ASP. Global_asax. Application_EndRequest() - threadid=2720, threadhash=1835, threadhash(agora)=1703, calldata= [4748] INFO 11:10:06,791 ASP. Global_asax. Application_EndRequest() - FIM /ConcurrentRequestsDemo/FastPage.aspx O segredo é o que acontece quando o Page_Load do FastPage é acionado. O ThreadID é 4748, mas o ThreadID que armazenei no HttpContext do ctor é 2720. O código hash para threads lógicas é 1703, mas o código hash que armazeno no ctor é 1835. Todos os dados que armazenei no CallContext estão faltando (até mesmo os dados marcados como ILogicalThreadAffinnative), mas o HttpContext ainda está lá. Como era de se esperar, meu ThreadStatic também sumiu.
O ponto chave é o que acontece quando o Page_Load do FastPage dispara. O ThreadID é 4748, mas o threadID que armazenei no HttpContext no ctor é 2720. O código hash da thread lógica é 1703, mas o que armazenei no ctor é 1835. Todos os dados que armazenei no CallContext sumiram (até mesmo aquele marcado como ILogicalThreadAffinative), mas o HttpContext ainda está lá. Como era de se esperar, meu ThreadStatic também sumiu. (Fim)
|