Acabei de descobrir que cada solicitação em um aplicativo Web ASP.Net obtém um bloqueio de sessão no início de uma solicitação e a libera no final da solicitação!
Caso as implicações disso sejam perdidas para você, como era para mim a princípio, isso basicamente significa o seguinte:
Sempre que uma página da Web ASP.Net está demorando muito para ser carregada (talvez devido a uma chamada lenta do banco de dados ou qualquer outra coisa), e o usuário decide que deseja navegar para uma página diferente porque está cansado de esperar, ELES NÃO PODEM! O bloqueio de sessão do ASP.Net força a nova solicitação de página a aguardar até que a solicitação original termine sua carga lenta e dolorosa. Arrrgh.
Sempre que um UpdatePanel estiver carregando lentamente, e o usuário decidir navegar para uma página diferente antes que o UpdatePanel termine de atualizar ... ELES NÃO PODEM! O bloqueio de sessão do ASP.net força a nova solicitação de página a aguardar até que a solicitação original termine sua carga lenta e dolorosa. Arrrgh dobro!
Então, quais são as opções? Até agora eu vim com:
- Implemente um SessionStateDataStore personalizado, suportado pelo ASP.Net. Eu não encontrei muitos por aí para copiar, e parece meio alto risco e fácil de estragar.
- Acompanhe todas as solicitações em andamento e, se uma solicitação vier do mesmo usuário, cancele a solicitação original. Parece meio extremo, mas funcionaria (eu acho).
- Não use Session! Quando eu precisar de algum tipo de estado para o usuário, eu poderia apenas usar o Cache e itens-chave no nome de usuário autenticado, ou algo assim. Mais uma vez parece meio extremo.
Eu realmente não posso acreditar que a equipe da ASP.Net Microsoft teria deixado um gargalo de desempenho tão grande na estrutura na versão 4.0! Estou perdendo algo óbvio? Quão difícil seria usar uma coleção ThreadSafe para a sessão?
Respostas:
Se sua página não modificar nenhuma variável de sessão, você poderá desativar a maior parte desse bloqueio.
Se sua página não ler nenhuma variável de sessão, você poderá desativar totalmente esse bloqueio para essa página.
Se nenhuma de suas páginas usar variáveis de sessão, basta desativar o estado da sessão no web.config.
Estou curioso, o que você acha que "uma coleção ThreadSafe" faria para se tornar segura para threads, se não usar bloqueios?
Edit: Eu provavelmente deveria explicar o que quero dizer com "desativar a maior parte desse bloqueio". Qualquer número de páginas de sessão somente leitura ou sem sessão pode ser processado para uma determinada sessão ao mesmo tempo sem se bloquear. No entanto, uma página de leitura-gravação-sessão não pode iniciar o processamento até que todas as solicitações somente leitura tenham sido concluídas e, enquanto estiver em execução, deve ter acesso exclusivo à sessão desse usuário para manter a consistência. O bloqueio de valores individuais não funcionaria, porque e se uma página alterar um conjunto de valores relacionados como um grupo? Como você garantiria que outras páginas em execução ao mesmo tempo obtivessem uma visualização consistente das variáveis de sessão do usuário?
Eu sugeriria que você tentasse minimizar a modificação de variáveis de sessão depois que elas fossem definidas, se possível. Isso permitiria que você aproveitasse a maioria das suas páginas como sessões de somente leitura, aumentando a chance de várias solicitações simultâneas do mesmo usuário não se bloquearem.
fonte
<pages enableSessionState="ReadOnly" />
no web.config e use @Page para ativar a gravação apenas em páginas específicas.OK, grandes adereços para Joel Muller por todas as suas contribuições. Minha solução definitiva foi usar o SessionStateModule personalizado, detalhado no final deste artigo do MSDN:
http://msdn.microsoft.com/en-us/library/system.web.sessionstate.sessionstateutility.aspx
Isto foi:
Isso fez uma enorme diferença no sentimento de "agitação" em nossa aplicação. Ainda não consigo acreditar que a implementação personalizada da sessão do ASP.Net bloqueie a sessão para toda a solicitação. Isso adiciona uma quantidade enorme de lentidão aos sites. A julgar pela quantidade de pesquisas on-line que eu tinha que fazer (e pelas conversas com vários desenvolvedores ASP.Net realmente experientes), muitas pessoas experimentaram esse problema, mas poucas pessoas chegaram ao fundo da causa. Talvez eu escreva uma carta para Scott Gu ...
Espero que isso ajude algumas pessoas por aí!
fonte
ReaderWriterLock
foi preterido em favor deReaderWriterLockSlim
- você deve usá-lo. Segundo,lock (typeof(...))
também foi preterido - você deve bloquear em uma instância de objeto estático particular. Terceiro, a frase "Este aplicativo não impede que solicitações simultâneas da Web usem o mesmo identificador de sessão" é um aviso, não um recurso.SessionStateItemCollection
código de exemplo por uma classe de thread-safe (talvez baseada emConcurrentDictionary
) se você quiser evitar erros difíceis de reproduzir sob carga.ISessionStateItemCollection
exige que aKeys
propriedade seja do tipoSystem.Collections.Specialized.NameObjectCollectionBase.KeysCollection
- que não possui construtores públicos. Puxa, obrigado pessoal. Isso é muito conveniente.Comecei a usar o AngiesList.Redis.RedisSessionStateModule , que além de usar o servidor Redis (muito rápido) para armazenamento (estou usando a porta do Windows - embora também exista uma porta MSOpenTech ), ele não bloqueia absolutamente a sessão .
Na minha opinião, se o seu aplicativo estiver estruturado de maneira razoável, isso não será um problema. Se você realmente precisar de dados consistentes e bloqueados como parte da sessão, implemente especificamente uma verificação de bloqueio / simultaneidade por conta própria.
A Microsoft que decide que todas as sessões do ASP.NET devem ser bloqueadas por padrão apenas para lidar com o design inadequado de aplicativos é uma decisão ruim, na minha opinião. Especialmente porque parece que a maioria dos desenvolvedores nem sequer percebeu que as sessões estavam bloqueadas, sem falar no fato de que os aplicativos aparentemente precisam ser estruturados para que você possa executar o estado da sessão somente leitura o máximo possível (desativar, sempre que possível) .
fonte
Eu preparei uma biblioteca com base nos links publicados neste tópico. Ele usa os exemplos do MSDN e CodeProject. Graças ao James.
Também fiz modificações aconselhadas por Joel Mueller.
O código está aqui:
https://github.com/dermeister0/LockFreeSessionState
Módulo HashTable:
Módulo ScaleOut StateServer:
Módulo personalizado:
Se você deseja implementar o suporte do Memcached ou Redis, instale este pacote. Em seguida, herde a classe LockFreeSessionStateModule e implemente métodos abstratos.
Alguns provedores de sessão sem bloqueio usando Redis:
fonte
A menos que seu aplicativo tenha necessidades especiais, acho que você tem 2 abordagens:
A sessão não é apenas segura para threads, mas também segura para o estado, de maneira que você saiba que até que a solicitação atual seja concluída, todas as variáveis de sessão não serão alteradas em relação a outra solicitação ativa. Para que isso aconteça, você deve garantir que a sessão SERÁ BLOQUEADA até que a solicitação atual seja concluída.
Você pode criar uma sessão como um comportamento de várias maneiras, mas se ela não bloquear a sessão atual, ela não será 'session'.
Para os problemas específicos que você mencionou, acho que você deve verificar HttpContext.Current.Response.IsClientConnected . Isso pode ser útil para evitar execuções e esperas desnecessárias no cliente, embora não possa resolver esse problema completamente, pois isso pode ser usado apenas por uma maneira de agrupamento e não por assinaturas.
fonte
Se você estiver usando o atualizado
Microsoft.Web.RedisSessionStateProvider
(a partir de3.0.2
), poderá adicioná-lo ao seuweb.config
para permitir sessões simultâneas.Fonte
fonte
Para o ASPNET MVC, fizemos o seguinte:
SessionStateBehavior.ReadOnly
todas as ações do controlador substituindoDefaultControllerFactory
SessionStateBehavior.Required
Crie ControllerFactory personalizado e substitua
GetControllerSessionBehavior
.AcquireSessionLockAttribute
Conecte a fábrica de controladores criada em
global.asax.cs
Agora, podemos ter os dois
read-only
eread-write
o estado da sessão em um únicoController
.fonte
Marcar o estado da sessão de um controlador como somente leitura ou desativado resolverá o problema.
Você pode decorar um controlador com o seguinte atributo para marcá-lo como somente leitura:
a enumeração System.Web.SessionState.SessionStateBehavior possui os seguintes valores:
fonte
Apenas para ajudar alguém com esse problema (bloqueio de solicitações ao executar outro da mesma sessão) ...
Hoje comecei a resolver esse problema e, após algumas horas de pesquisa, resolvi-o removendo o
Session_Start
método (mesmo que vazio) do arquivo Global.asax .Isso funciona em todos os projetos que testei.
fonte
Session_Start
método e ainda bloqueiaDepois de lutar com todas as opções disponíveis, acabei escrevendo um provedor SessionStore baseado em token JWT (a sessão viaja dentro de um cookie e não é necessário armazenamento de back-end).
http://www.drupalonwindows.com/en/content/token-sessionstate
Vantagens:
fonte