Posso usar threads para realizar trabalhos de longa duração no IIS?

85

Em um aplicativo ASP.Net, o usuário clica em um botão na página da Web e isso instancia um objeto no servidor por meio do manipulador de eventos e chama um método no objeto. O método vai para um sistema externo para fazer coisas e isso pode demorar um pouco. Então, o que eu gostaria de fazer é executar essa chamada de método em outro thread para que eu possa retornar o controle ao usuário com "Sua solicitação foi enviada". Estou razoavelmente feliz em fazer isso como disparar e esquecer, embora seria ainda melhor se o usuário pudesse continuar pesquisando o status do objeto.

O que não sei é se o IIS permite que meu thread continue em execução, mesmo que a sessão do usuário expire. Imagine, o usuário dispara o evento e nós instanciamos o objeto no servidor e disparamos o método em uma nova thread. O usuário fica satisfeito com a mensagem "Sua solicitação foi enviada" e fecha o navegador. Eventualmente, esta sessão de usuário atingirá o tempo limite no IIS, mas o thread ainda pode estar em execução, funcionando. O IIS permitirá que o thread continue em execução ou irá eliminá-lo e descartar o objeto quando a sessão do usuário expirar?

EDIT: Pelas respostas e comentários, entendo que a melhor maneira de fazer isso é mover o processamento de longa duração para fora do IIS. Além de tudo, isso lida com o problema de reciclagem de appdomain. Na prática, preciso tirar a versão 1 do papel em tempo limitado e trabalhar dentro de uma estrutura existente, portanto, gostaria de evitar a camada de serviço, daí o desejo de apenas disparar o thread dentro do IIS. Na prática, "longa duração" aqui levará apenas alguns minutos e a concorrência no site será baixa, portanto, deve estar tudo bem. Mas, a próxima versão definitivamente precisará ser dividida em uma camada de serviço separada.

Frans
fonte
1
Você tem mais detalhes sobre o que precisa ser processado e seu ambiente de hospedagem? Por exemplo, você consegue instalar um serviço?
senfo
Você sempre pode usar os métodos de programação Async e Await (Visual Studio 2012+). Muito mais limpo do que gerenciar tópicos você mesmo.
SausageFingers de

Respostas:

65

Você pode realizar o que deseja, mas normalmente é uma má ideia. Vários blogs ASP.NET e mecanismos CMS adotam essa abordagem, porque desejam ser instaláveis ​​em um sistema de hospedagem compartilhado e não depender de um serviço do Windows que precise ser instalado. Normalmente, eles iniciam um thread de longa execução em Global.asax quando o aplicativo é iniciado e têm esse processo de thread em fila de tarefas.

Além de reduzir os recursos disponíveis para IIS / ASP.NET para processar solicitações, você também tem problemas com o thread sendo eliminado quando o AppDomain é reciclado e, então, você tem que lidar com a persistência da tarefa enquanto ela está em andamento, como bem como reiniciar o trabalho quando o AppDomain voltar a funcionar.

Lembre-se de que, em muitos casos, o AppDomain é reciclado automaticamente em um intervalo padrão, bem como se você atualizar o web.config, etc.

Se você pode lidar com a persistência e os aspectos transacionais de seu thread sendo eliminados a qualquer momento, então você pode contornar a reciclagem de AppDomain tendo algum processo externo que faz uma solicitação em seu site em algum intervalo - para que, se o site for reciclado, você tem a garantia de que ele será reiniciado automaticamente em X minutos.

Novamente, isso normalmente é uma má ideia.

EDITAR: Aqui estão alguns exemplos dessa técnica em ação:

Community Server: Usando Windows Services vs. Thread de Segundo Plano para Executar Código em Intervalos Agendados Criando um Thread de Segundo Plano quando o Site é iniciado pela primeira vez

EDIT (de um futuro distante) - Hoje em dia eu usaria o Hangfire .

Bryan Batchelder
fonte
1
Obrigado, isso é perspicaz. A reciclagem é um assassino definitivo e deduzo do que você está dizendo que posso fazer isso como uma coisa dura e rápida, mas é perigosa.
Frans
Sim, verifique aqui: professionalaspnet.com/archive/2007/09/02/… E aqui está um tópico sobre como o COmmunity Server lida com a mesma coisa: dev.communityserver.com/forums/t/459942.aspx
Bryan Batchelder
No meu caso, consegui apenas dizer ao IIS para nunca reciclar e nunca deixar o site inativo. Isso fez com que eu pudesse trabalhar sob esse pressuposto e que o site só seria reciclado durante a implantação e sempre que ocorresse a manutenção planejada. Como eu estava apenas criando um site do tipo administrador, funcionou perfeitamente para mim.
Brett
1
Bizarro que isso tenha tantos votos positivos. Que isso seja possível é uma das coisas muito legais sobre ASP.NET: que todas as solicitações sejam atendidas no mesmo AppDomain junto com quaisquer threads de segundo plano que você possa criar. Nenhuma das razões dadas por que esta é uma "má ideia" me parece convincente. Os AppDomains podem cair, sim - mas isso dificilmente é exclusivo para o ambiente ASP.NET. Você precisa jogar bem com seu ambiente de hospedagem (google para HostingEnvironment.RegisterObject).
John
3
.NET 4.5.2 adicionou um novo recurso QueueBackgroundWorkItempara registrar facilmente tarefas em segundo plano, você pode querer atualizar sua resposta novamente.
Scott Chamberlain
40

Não concordo com a resposta aceita.

Usar um thread de segundo plano (ou uma tarefa, iniciada com Task.Factory.StartNew) é bom no ASP.NET. Como acontece com todos os ambientes de hospedagem, você pode querer entender e cooperar com as instalações que regem o desligamento.

No ASP.NET, você pode registrar o trabalho que precisa ser interrompido normalmente no desligamento usando o HostingEnvironment.RegisterObjectmétodo. Veja este artigo e os comentários para uma discussão.

(Como Gerard aponta em seu comentário, agora há também HostingEnvironment.QueueBackgroundWorkItemque chama RegisterObjectpara registrar um agendador para o item de plano de fundo trabalhar. No geral, o novo método é mais agradável, pois é baseado em tarefas.)

Quanto ao tema geral que você costuma ouvir dizer que é uma má ideia, considere a alternativa de implantar um serviço do Windows (ou outro tipo de aplicativo de processo extra):

  • Não há mais implantação trivial com implantação na web
  • Não implantável puramente em sites do Azure
  • Dependendo da natureza da tarefa em segundo plano, os processos provavelmente terão que se comunicar. Isso significa que alguma forma de IPC ou o serviço terá que acessar um banco de dados comum.

Observe também que alguns cenários avançados podem até mesmo precisar que o thread de segundo plano seja executado no mesmo espaço de endereço que as solicitações. Vejo o fato de que o ASP.NET pode fazer isso como uma grande vantagem que se tornou possível por meio do .NET.

John
fonte
Isso é ótimo. Tenho uma tarefa que precisa de cerca de 30 segundos para ser concluída. Perfeito se você só precisa atrasar o pool de aplicativos por tempo suficiente para fazer o trabalho.
Scuba Steve
1
No .Net Framework 4.5.2 você também pode usar HostingEnvironment.QueueBackgroundWorkItem (Action <CancellationToken>)
Gerard Carbó
Em minhas observações, parece que o IIS finalmente desliga a API da Web ASP.NET que estou executando e não a inicia de volta até que uma nova solicitação chegue. Portanto, na hora marcada em que meu thread deve iniciar o aplicativo pode nem mesmo estar correndo. Eu estou errado sobre isso?
David Sopko
7

Você não gostaria de usar um encadeamento do pool de encadeamentos do IIS para esta tarefa porque isso o deixaria incapaz de processar solicitações futuras. Você poderia examinar as páginas assíncronas no ASP.NET 2.0 , mas essa também não seria a resposta certa. Em vez disso, parece que você se beneficiaria com o Microsoft Message Queuing . Basicamente, você adicionaria os detalhes da tarefa à fila e outro processo em segundo plano (possivelmente um serviço do Windows) seria o encarregado de realizar essa tarefa. Mas o ponto principal é que o processo em segundo plano é completamente isolado do IIS.

senfo
fonte
Ah, MSMQ - uma das minhas tecnologias favoritas de longo prazo. Obrigado pelo insight e pelo link.
Frans
6

Eu sugeriria usar HangFire para tais requisitos. É um bom mecanismo de fogo e esquecer que funciona em segundo plano, suporta arquiteturas diferentes, confiável porque é apoiado por armazenamento de persistência.

Vipul
fonte
5

Há um bom tópico e código de amostra aqui: http://forums.asp.net/t/1534903.aspx?PageIndex=2

Eu até brinquei com a ideia de chamar uma página keep alive no meu site a partir do thread para ajudar a manter o pool de aplicativos ativo. Lembre-se de que, se estiver usando esse método, você precisa de um tratamento de recuperação realmente bom, porque o aplicativo pode ser reciclado a qualquer momento. Como muitos já mencionaram, esta não é a abordagem certa se você tiver acesso a outras opções de serviço, mas para hospedagem compartilhada esta pode ser uma de suas únicas opções.

Para ajudar a manter o pool de aplicativos ativo, você pode fazer uma solicitação ao seu próprio site enquanto o thread está sendo processado. Isso pode ajudar a manter o pool de aplicativos ativo se o processo for executado por muito tempo.

string tempStr = GetUrlPageSource("http://www.mysite.com/keepalive.aspx");


    public static string GetUrlPageSource(string url)
    {
        string returnString = "";

        try
        {
            Uri uri = new Uri(url);
            if (uri.Scheme == Uri.UriSchemeHttp)
            {
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
                CookieContainer cookieJar = new CookieContainer();

                req.CookieContainer = cookieJar;

                //set the request timeout to 60 seconds
                req.Timeout = 60000;
                req.UserAgent = "MyAgent";

                //we do not want to request a persistent connection
                req.KeepAlive = false;

                HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
                Stream stream = resp.GetResponseStream();
                StreamReader sr = new StreamReader(stream);

                returnString = sr.ReadToEnd();

                sr.Close();
                stream.Close();
                resp.Close();
            }
        }
        catch
        {
            returnString = "";
        }

        return returnString;
    }
Daniel
fonte
5

Começamos esse caminho e funcionou bem quando nosso aplicativo estava em um servidor. Quando queríamos escalar para várias máquinas (ou usar vários w3wp em um web garen), tínhamos que reavaliar e ver como gerenciar uma fila de trabalho, tratamento de erros, novas tentativas e o complicado problema de bloqueio correto para garantir apenas o servidor pega o próximo item.

... percebemos que não estamos no negócio de escrever motores de processamento em segundo plano, então procuramos as soluções existentes e acabamos usando o incrível projeto OSS hangfire

Sergey Odinokov criou uma verdadeira joia que é realmente fácil de começar e permite que você troque o backend de como o trabalho é persistido e enfileirado. Hangfire usa threads de fundo, mas persiste os trabalhos, lida com novas tentativas e dá a você visibilidade na fila de trabalho. Portanto, os trabalhos hangfire são robustos e sobrevivem a todos os caprichos de appdomains sendo reciclados etc.

Sua configuração básica usa sql server como armazenamento, mas você pode trocar por Redis ou MSMQ quando for hora de aumentar a escala. Ele também possui uma excelente interface do usuário para visualizar todos os trabalhos e seus status, além de permitir que você recoloque os trabalhos na fila.

Meu ponto é que, embora seja inteiramente possível fazer o que você deseja em um thread de segundo plano, há muito trabalho para torná-lo escalável e robusto. É bom para cargas de trabalho simples, mas quando as coisas ficam mais complexas, prefiro usar uma biblioteca construída para esse fim em vez de fazer esse esforço.

Para obter mais perspectivas sobre as opções disponíveis, verifique o blog de Scott Hanselman, que aborda algumas opções para lidar com trabalhos em segundo plano no asp.net. (Ele deu uma crítica brilhante ao hangfire)

Também como referenciado por John, vale a pena ler o blog de Phil Haack sobre por que a abordagem é problemática e como interromper o trabalho no encadeamento quando appdomain é descarregado.

Avner
fonte
2

Você pode criar um serviço do Windows para fazer essa tarefa? Em seguida, use o .NET remoting do servidor da Web para chamar o serviço do Windows para fazer a ação? Se for esse o caso, é o que eu faria.

Isso eliminaria a necessidade de retransmissão no IIS e limitaria parte de seu poder de processamento.

Do contrário, eu forçaria o usuário a ficar sentado enquanto o processo é concluído. Dessa forma, você garante que ele seja concluído e não eliminado pelo IIS.

David Basarab
fonte
2

Parece haver uma maneira com suporte de hospedar trabalhos de longa execução no IIS. Os serviços de fluxo de trabalho parecem projetados para isso, especialmente em conjunto com o Windows Server AppFabric . O design permite a reciclagem do pool de aplicativos, suportando a persistência automática e a retomada do trabalho de longa execução.

Olly
fonte
2

Você pode executar tarefas em segundo plano e elas serão concluídas mesmo após o término da solicitação. Não deixe uma exceção não detectada ser lançada . Normalmente, você deseja sempre lançar suas exceções. Se uma exceção for lançada em um novo thread, ele travará o processo de trabalho do IIS - w3wp.exe , porque você não está mais no contexto da solicitação. Isso também eliminará quaisquer outras tarefas em segundo plano que você tenha em execução, além das sessões com backup de memória em processo, se você as estiver usando. Isso seria difícil de diagnosticar, por isso a prática é desencorajada.

Timothy Gonzalez
fonte
1

Basta criar um processo substituto para executar as tarefas assíncronas; ele não precisa ser um serviço do Windows (embora essa seja a abordagem mais ideal na maioria dos casos. MSMQ é mais do que kill.

Matt Davison
fonte