Sempre tive a impressão de que usar o ThreadPool para tarefas em segundo plano (digamos não críticas) de curta duração era considerado uma prática recomendada, mesmo em ASP.NET, mas então me deparei com este artigo que parece sugerir o contrário - o argumento sendo que você deve deixar o ThreadPool para lidar com solicitações relacionadas ao ASP.NET.
Então, aqui está como tenho feito pequenas tarefas assíncronas até agora:
ThreadPool.QueueUserWorkItem(s => PostLog(logEvent))
E o artigo sugere, em vez disso, criar um tópico explicitamente, semelhante a:
new Thread(() => PostLog(logEvent)){ IsBackground = true }.Start()
O primeiro método tem a vantagem de ser gerenciado e limitado, mas existe o potencial (se o artigo estiver correto) de que as tarefas em segundo plano estejam disputando threads com manipuladores de solicitações ASP.NET. O segundo método libera o ThreadPool, mas ao custo de ser ilimitado e, portanto, potencialmente usar muitos recursos.
Portanto, minha pergunta é: o conselho do artigo está correto?
Se seu site estava recebendo tanto tráfego que sua ThreadPool estava ficando cheia, então é melhor sair da banda, ou uma ThreadPool cheia implicaria que você está chegando ao limite de seus recursos de qualquer maneira, nesse caso você não deveria tentar iniciar seus próprios tópicos?
Esclarecimento: estou apenas perguntando no escopo de pequenas tarefas assíncronas não críticas (por exemplo, registro remoto), itens de trabalho não caros que exigiriam um processo separado (nesses casos, concordo que você precisará de uma solução mais robusta).
fonte
Respostas:
Outras respostas aqui parecem estar deixando de fora o ponto mais importante:
A menos que você esteja tentando paralelizar uma operação com uso intensivo de CPU para fazê-la mais rapidamente em um site de baixa carga, não há nenhum ponto em usar um thread de trabalho.
Isso vale para threads livres, criados por
new Thread(...)
, e threads de trabalho noThreadPool
que respondem àsQueueUserWorkItem
solicitações.Sim, é verdade, você pode privar o
ThreadPool
processo em um ASP.NET enfileirando muitos itens de trabalho. Isso impedirá que o ASP.NET processe outras solicitações. As informações no artigo são precisas a esse respeito; o mesmo pool de threads usado paraQueueUserWorkItem
também é usado para atender a solicitações.Mas se você está realmente enfileirando itens de trabalho suficientes para causar essa privação, então você deve estar privando o pool de threads! Se você estiver executando literalmente centenas de operações com uso intensivo de CPU ao mesmo tempo, de que adiantaria ter outro thread de trabalho para atender a uma solicitação ASP.NET, quando a máquina já está sobrecarregada? Se você está passando por essa situação, precisa redesenhar completamente!
Na maioria das vezes, vejo ou ouço falar de código multithread sendo usado de maneira inadequada no ASP.NET, não é para enfileirar trabalhos com uso intensivo de CPU. É para enfileirar o trabalho vinculado a E / S. E se você quiser fazer o trabalho de E / S, deve usar um thread de E / S (Porta de Conclusão de E / S).
Especificamente, você deve usar os retornos de chamada assíncronos suportados por qualquer classe de biblioteca que estiver usando. Esses métodos são sempre identificados de forma muito clara; eles começam com as palavras
Begin
eEnd
. Como emStream.BeginRead
,Socket.BeginConnect
,WebRequest.BeginGetResponse
e assim por diante.Estes métodos fazem usar o
ThreadPool
, mas eles usam IOCPs, que não não interferem com os pedidos do ASP.NET. Eles são um tipo especial de thread leve que pode ser "ativado" por um sinal de interrupção do sistema de E / S. E em um aplicativo ASP.NET, você normalmente tem um thread de E / S para cada thread de trabalho, portanto, cada solicitação pode ter uma operação assíncrona enfileirada. Isso significa literalmente centenas de operações assíncronas sem qualquer degradação significativa do desempenho (supondo que o subsistema de E / S possa acompanhar). É muito mais do que você precisa.Apenas tenha em mente que os delegados assíncronos não funcionam dessa maneira - eles acabarão usando um thread de trabalho, assim como
ThreadPool.QueueUserWorkItem
. Somente os métodos assíncronos internos das classes da biblioteca do .NET Framework são capazes de fazer isso. Você pode fazer isso sozinho, mas é complicado e um pouco perigoso e provavelmente está além do escopo desta discussão.A melhor resposta a essa pergunta, em minha opinião, é não usar o
ThreadPool
ou umaThread
instância de plano de fundo no ASP.NET . Não é como criar um thread em um aplicativo Windows Forms, onde você faz isso para manter a IU responsiva e não se preocupa com sua eficiência. No ASP.NET, sua preocupação é a taxa de transferência , e todo esse contexto alternando todos os threads de trabalho irá absolutamente matar sua taxa de transferência, quer você useThreadPool
ou não.Por favor, se você está escrevendo um código de threading em ASP.NET - considere se ele pode ou não ser reescrito para usar métodos assíncronos pré-existentes, e se não puder, então considere se você realmente precisa ou não do código para ser executado em um thread em segundo plano. Na maioria dos casos, você provavelmente estará adicionando complexidade sem nenhum benefício líquido.
fonte
Action<T>
como retorno de chamada, por exemplo. Se você quer dizer que a escolha de usar um thread de trabalho ou thread de E / S ocorre no nível mais baixo, isso é intencional; apenas esse nível pode decidir se precisa ou não de um IOCP.ThreadPool
que limita você dessa forma, provavelmente porque eles não confiaram nos desenvolvedores para fazer isso direito. O Windows Thread Pool não gerenciado possui uma API muito semelhante, mas na verdade permite que você escolha o tipo de thread.De acordo com Thomas Marquadt da equipe ASP.NET da Microsoft, é seguro usar o ASP.NET ThreadPool (QueueUserWorkItem).
Do artigo :
fonte
Os sites não devem gerar tópicos.
Normalmente, você move essa funcionalidade para um serviço do Windows com o qual se comunica (eu uso o MSMQ para falar com eles).
- Editar
Descrevi uma implementação aqui: Processamento em segundo plano baseado em fila no aplicativo da Web ASP.NET MVC
- Editar
Para expandir por que isso é ainda melhor do que apenas tópicos:
Usando o MSMQ, você pode se comunicar com outro servidor. Você pode gravar em uma fila entre máquinas, portanto, se determinar, por algum motivo, que sua tarefa em segundo plano está usando demais os recursos do servidor principal, você pode simplesmente deslocá-la de maneira bastante trivial.
Também permite que você processe em lote qualquer tarefa que esteja tentando fazer (enviar e-mails / qualquer outra coisa).
fonte
Eu definitivamente acho que a prática geral para trabalho assíncrono rápido e de baixa prioridade no ASP.NET seria usar o pool de threads do .NET, especialmente para cenários de alto tráfego, pois você deseja que seus recursos sejam limitados.
Além disso, a implementação do threading está oculta - se você começar a gerar seus próprios threads, também terá que gerenciá-los adequadamente. Não estou dizendo que você não poderia fazer isso, mas por que reinventar essa roda?
Se o desempenho se tornar um problema e você puder estabelecer que o pool de threads é o fator limitante (e não as conexões de banco de dados, conexões de rede de saída, memória, tempos limite de página, etc.), você ajusta a configuração do pool de threads para permitir mais threads de trabalho, solicitações em fila de espera etc.
Se você não tiver um problema de desempenho, escolher gerar novos threads para reduzir a contenção com a fila de solicitações ASP.NET é uma otimização prematura clássica.
O ideal é que você não precise usar um thread separado para fazer uma operação de registro - apenas habilite o thread original para concluir a operação o mais rápido possível, que é onde o MSMQ e um thread / processo de consumidor separado entram em cena. Eu concordo que isso é mais pesado e mais trabalhoso para implementar, mas você realmente precisa da durabilidade aqui - a volatilidade de uma fila compartilhada na memória irá rapidamente se desgastar.
fonte
Você deve usar QueueUserWorkItem e evitar a criação de novos encadeamentos como faria para evitar a praga. Para obter um visual que explique por que você não vai matar de fome o ASP.NET, já que ele usa o mesmo ThreadPool, imagine um malabarista muito habilidoso usando duas mãos para manter meia dúzia de pinos de boliche, espadas ou o que quer que seja em vôo. Para ter uma ideia de por que criar seus próprios fios é ruim, imagine o que acontece em Seattle na hora do rush, quando rampas de entrada muito usadas para a rodovia permitem que os veículos entrem no trânsito imediatamente em vez de usar um semáforo e limitar o número de entradas a uma a cada poucos segundos . Finalmente, para uma explicação detalhada, consulte este link:
http://blogs.msdn.com/tmarq/archive/2010/04/14/performing-asynchronous-work-or-tasks-in-asp-net-applications.aspx
Obrigado Thomas
fonte
Esse artigo não está correto. ASP.NET tem seu próprio pool de threads, threads de trabalho gerenciados, para atender a solicitações ASP.NET. Esse pool geralmente tem algumas centenas de threads e é separado do pool ThreadPool, que é um múltiplo menor de processadores.
O uso de ThreadPool no ASP.NET não interfere nos threads de trabalho do ASP.NET. Usar ThreadPool é bom.
Também seria aceitável configurar um único encadeamento que fosse apenas para registrar mensagens e usar o padrão produtor / consumidor para passar mensagens de log para esse encadeamento. Nesse caso, como o encadeamento tem longa duração, você deve criar um único encadeamento novo para executar o registro.
Usar um novo tópico para cada mensagem é definitivamente um exagero.
Outra alternativa, se você estiver falando apenas sobre registro, é usar uma biblioteca como o log4net. Ele lida com o log em um thread separado e cuida de todos os problemas de contexto que podem surgir nesse cenário.
fonte
Eu diria que o artigo está errado. Se você estiver executando uma grande loja .NET, você pode usar o pool com segurança em vários aplicativos e sites (usando pools de aplicativos separados), simplesmente com base em uma instrução na documentação ThreadPool :
fonte
System.Threading.ThreadPool
.Eu fui questionado sobre uma pergunta semelhante no trabalho na semana passada e darei a você a mesma resposta. Por que você está usando aplicativos da web multi-threading por solicitação? Um servidor web é um sistema fantástico, altamente otimizado para fornecer muitas solicitações em tempo hábil (isto é, multi-threading). Pense no que acontece quando você solicita quase todas as páginas da web.
Você dá o exemplo de registro remoto, mas isso deve ser uma preocupação do seu logger. Um processo assíncrono deve estar em vigor para receber mensagens em tempo hábil. Sam até aponta que seu logger (log4net) já deve suportar isso.
Sam também está correto ao dizer que usar o Thread Pool no CLR não causará problemas com o thread pool no IIS. A coisa a se preocupar aqui, porém, é que você não está gerando threads de um processo, você está gerando novos threads de threads de pool de threads do IIS. Existe uma diferença e a distinção é importante.
Fonte
fonte
Não concordo com o artigo referenciado (C # feeds.com). É fácil criar um novo tópico, mas perigoso. O número ideal de threads ativos a serem executados em um único núcleo é surpreendentemente baixo - menos de 10. É muito fácil fazer com que a máquina perca tempo trocando threads se os threads forem criados para tarefas menores. Threads são recursos que REQUEREM gerenciamento. A abstração WorkItem existe para lidar com isso.
Há uma troca aqui entre reduzir o número de threads disponíveis para solicitações e criar muitos threads para permitir que qualquer um deles seja processado com eficiência. Esta é uma situação muito dinâmica, mas acho que deve ser gerenciada ativamente (neste caso pelo pool de threads) em vez de deixar para o processador ficar à frente da criação de threads.
Finalmente, o artigo faz algumas afirmações bem abrangentes sobre os perigos de usar o ThreadPool, mas realmente precisa de algo concreto para fazer o backup.
fonte
Parece difícil obter uma resposta definitiva para saber se o IIS usa ou não o mesmo ThreadPool para lidar com solicitações de entrada. Portanto, parece uma boa ideia não usar threads de ThreadPool excessivamente, para que o IIS tenha muitos deles disponíveis. Por outro lado, criar seu próprio thread para cada pequena tarefa parece uma má ideia. Presumivelmente, você tem algum tipo de bloqueio em seu registro, então apenas um thread poderia progredir por vez, e o resto se revezaria sendo programado e não programado (para não mencionar a sobrecarga de gerar um novo thread). Essencialmente, você se depara com os problemas exatos que o ThreadPool foi projetado para evitar.
Parece que um compromisso razoável seria seu aplicativo alocar um único thread de registro para o qual você pudesse passar mensagens. Você deve ter cuidado para que o envio de mensagens seja o mais rápido possível, para não deixar seu aplicativo lento.
fonte
Você pode usar Parallel.For ou Parallel.ForEach e definir o limite de encadeamentos possíveis que deseja alocar para funcionar sem problemas e evitar a privação de pool.
No entanto, sendo executado em segundo plano, você precisará usar o estilo TPL puro abaixo no aplicativo da web ASP.Net.
fonte