As operações assíncronas no ASP.NET MVC usam um thread do ThreadPool no .NET 4

158

Após essa pergunta, me sinto confortável ao usar operações assíncronas no ASP.NET MVC. Então, eu escrevi dois posts sobre isso:

Tenho muitos mal-entendidos em mente sobre operações assíncronas no ASP.NET MVC.

Eu sempre ouço esta frase: o aplicativo pode ser melhor dimensionado se as operações forem executadas de forma assíncrona

E também ouvi esse tipo de frase: se você tem um grande volume de tráfego, é melhor não executar suas consultas de forma assíncrona - consumir 2 threads extras para atender a uma solicitação retira recursos de outras solicitações recebidas.

Eu acho que essas duas frases são inconsistentes.

Não tenho muitas informações sobre como o threadpool funciona no ASP.NET, mas sei que o threadpool tem um tamanho limitado para threads. Portanto, a segunda frase deve estar relacionada a esse problema.

E gostaria de saber se as operações assíncronas no ASP.NET MVC usam um thread do ThreadPool no .NET 4?

Por exemplo, quando implementamos um AsyncController, como o aplicativo estrutura? Se eu receber um tráfego enorme, é uma boa ideia implementar o AsyncController?

Existe alguém por aí que possa tirar essa cortina preta diante dos meus olhos e me explicar o acordo sobre assincronia no ASP.NET MVC 3 (NET 4)?

Editar:

Eu li o documento abaixo quase centenas de vezes e compreendo o negócio principal, mas ainda tenho confusão porque há muitos comentários inconsistentes por aí.

Usando um controlador assíncrono no asp.net MVC

Editar:

Vamos supor que eu tenha uma ação do controlador como abaixo (não uma implementação do AsyncControllerque):

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

Como você vê aqui, eu aciono uma operação e a esqueço. Então, volto imediatamente sem esperar que seja concluída.

Nesse caso, isso precisa usar um thread do threadpool? Se sim, após a conclusão, o que acontece com esse segmento? Ele GCentra e limpa logo após a conclusão?

Editar:

Para a resposta do @ Darin, aqui está um exemplo de código assíncrono que fala com o banco de dados:

public class FooController : AsyncController {

    //EF 4.2 DbContext instance
    MyContext _context = new MyContext();

    public void IndexAsync() { 

        AsyncManager.OutstandingOperations.Increment(3);

        Task<IEnumerable<Foo>>.Factory.StartNew(() => { 

           return 
                _context.Foos;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foos"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<Bars>>.Factory.StartNew(() => { 

           return 
                _context.Bars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["bars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<FooBar>>.Factory.StartNew(() => { 

           return 
                _context.FooBars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foobars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });
    }

    public ViewResult IndexCompleted(
        IEnumerable<Foo> foos, 
        IEnumerable<Bar> bars,
        IEnumerable<FooBar> foobars) {

        //Do the regular stuff and return

    }
}
tugberk
fonte
Não tenho certeza da resposta, mas vale a pena notar que assíncrono e multiencadeamento são coisas diferentes. Portanto, seria possível ter um número fixo de threads com manipulação assíncrona. O que aconteceria é quando uma página tem que bloquear para dizer, E / S, outra página terá a chance de ser executada no mesmo encadeamento. É por isso que essas duas afirmações podem ser verdadeiras: o assíncrono pode tornar as coisas mais rápidas, mas muitos threads são um problema.
precisa saber é o seguinte
@ChrisChilvers Sim, o multithreading nem sempre é necessário na operação assíncrona. Eu já percebi isso, mas acho que não tenho controle sobre isso, tanto quanto posso dizer. O AsyncController gera quantos threads ele quer do meu ponto de vista, mas também não tem certeza. Existe uma noção de pool de threads em aplicativos de desktop como o WPF também? Acho que o número de threads não é um problema para esse tipo de aplicativo.
tugberk
6
Dê uma olhada no vídeo Threading with Jeff Richter
oleksii
Eu acho que o problema (e, portanto, inconsistência) é a segunda declaração usa assíncrona quando isso significa muitos threads. Isso pode ser porque é assim que o asp.net implementou páginas assíncronas e, portanto, a implementação específica confundiu o problema (já que o nome do recurso que causa o problema seria páginas assíncronas), mas não tenho certeza sobre a implementação específica . Portanto, eles significam "muitos threads" ou "páginas assíncronas no asp.net versão X", pois versões futuras podem alterar a implementação. Ou eles apenas significam o uso do pool de threads para executar assíncrona dentro de uma página.
precisa saber é o seguinte
@ChrisChilvers oh, man! Estou mais confuso após estes comentários: s
tugberk

Respostas:

177

Aqui está um excelente artigo que eu recomendo que você leia para entender melhor o processamento assíncrono no ASP.NET (que é basicamente o que os controladores assíncronos representam).

Vamos primeiro considerar uma ação síncrona padrão:

public ActionResult Index()
{
    // some processing
    return View();
}

Quando uma solicitação é feita para essa ação, um encadeamento é retirado do conjunto de encadeamentos e o corpo dessa ação é executado nesse encadeamento. Portanto, se o processamento dentro dessa ação for lento, você estará bloqueando esse encadeamento durante todo o processamento, portanto, esse encadeamento não poderá ser reutilizado para processar outras solicitações. No final da execução da solicitação, o encadeamento é retornado ao conjunto de encadeamentos.

Agora vamos dar um exemplo do padrão assíncrono:

public void IndexAsync()
{
    // perform some processing
}

public ActionResult IndexCompleted(object result)
{
    return View();
}

Quando uma solicitação é enviada para a ação Índice, um thread é desenhado do pool de threads e o corpo do IndexAsyncmétodo é executado. Depois que o corpo desse método termina a execução, o thread é retornado ao pool de threads. Em seguida, usando o padrão AsyncManager.OutstandingOperations, depois de sinalizar a conclusão da operação assíncrona, outro encadeamento é retirado do conjunto de encadeamentos e o corpo da IndexCompletedação é executado nele e o resultado é renderizado ao cliente.

Portanto, o que podemos ver nesse padrão é que uma única solicitação HTTP do cliente pode ser executada por dois threads diferentes.

Agora a parte interessante acontece dentro do IndexAsyncmétodo. Se você tiver uma operação de bloqueio, estará desperdiçando totalmente todo o propósito dos controladores assíncronos porque está bloqueando o encadeamento de trabalho (lembre-se de que o corpo desta ação é executado em um encadeamento retirado do conjunto de encadeamentos).

Então, quando podemos tirar proveito real dos controladores assíncronos, você pode perguntar?

IMHO, podemos ganhar mais quando temos operações intensivas de E / S (como chamadas de banco de dados e rede para serviços remotos). Se você tiver uma operação intensiva de CPU, ações assíncronas não trarão muitos benefícios.

Então, por que podemos obter benefícios com operações intensivas de E / S? Porque nós poderíamos usar portas I / O de conclusão . O IOCP é extremamente poderoso porque você não consome nenhum encadeamento ou recurso no servidor durante a execução de toda a operação.

Como eles funcionam?

Suponha que desejemos baixar o conteúdo de uma página da Web remota usando o método WebClient.DownloadStringAsync . Você chama esse método que registrará um IOCP no sistema operacional e retornará imediatamente. Durante o processamento de toda a solicitação, nenhum thread é consumido no seu servidor. Tudo acontece no servidor remoto. Isso pode levar muito tempo, mas você não se importa, pois não está comprometendo seus threads de trabalho. Depois que uma resposta é recebida, o IOCP é sinalizado, um encadeamento é retirado do conjunto de encadeamentos e o retorno de chamada é executado nesse encadeamento. Mas como você pode ver, durante todo o processo, não monopolizamos nenhum thread.

O mesmo vale para métodos como FileStream.BeginRead, SqlCommand.BeginExecute, ...

Que tal paralelizar várias chamadas de banco de dados? Suponha que você tenha realizado uma ação de controlador síncrona na qual executou 4 chamadas de banco de dados de bloqueio em sequência. É fácil calcular que, se cada chamada do banco de dados demorar 200ms, a ação do seu controlador levará aproximadamente 800ms para ser executada.

Se você não precisar executar essas chamadas seqüencialmente, paralelizá-las melhoraria o desempenho?

Essa é a grande questão, que não é fácil de responder. Talvez sim, talvez não. Depende inteiramente de como você implementa essas chamadas ao banco de dados. Se você usar controladores assíncronos e portas de conclusão de E / S, conforme discutido anteriormente, você aumentará o desempenho dessa ação do controlador e de outras ações, pois não monopolizará os encadeamentos de trabalho.

Por outro lado, se você implementá-las de maneira inadequada (com uma chamada de banco de dados de bloqueio realizada em um encadeamento do pool de encadeamentos), você basicamente reduzirá o tempo total de execução dessa ação para aproximadamente 200ms, mas consumiria 4 encadeamentos de trabalho para que você pode ter prejudicado o desempenho de outras solicitações que podem passar fome devido à falta de threads no pool para processá-las.

Portanto, é muito difícil e se você não se sentir pronto para executar testes extensivos em seu aplicativo, não implemente controladores assíncronos, pois é possível que você cause mais danos do que benefícios. Implemente-os apenas se você tiver um motivo para fazê-lo: por exemplo, você identificou que as ações padrão do controlador síncrono são um gargalo para o seu aplicativo (depois de executar testes de carga e medições abrangentes).

Agora vamos considerar o seu exemplo:

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

Quando uma solicitação é recebida para a ação Índice, um encadeamento é retirado do conjunto de encadeamentos para executar seu corpo, mas seu corpo apenas agenda uma nova tarefa usando o TPL . Portanto, a execução da ação termina e o encadeamento é retornado ao conjunto de encadeamentos. Exceto que, o TPL usa threads do pool de threads para executar seu processamento. Portanto, mesmo que o encadeamento original tenha sido retornado ao conjunto de encadeamentos, você desenhou outro encadeamento desse conjunto para executar o corpo da tarefa. Então você colocou em risco 2 threads do seu pool precioso.

Agora vamos considerar o seguinte:

public ViewResult Index() { 

    new Thread(() => { 
        //Do an advanced looging here which takes a while
    }).Start();

    return View();
}

Nesse caso, estamos gerando um thread manualmente. Nesse caso, a execução do corpo da ação Index pode demorar um pouco mais (porque gerar um novo thread é mais caro do que extrair um de um pool existente). Mas a execução da operação avançada de log será feita em um encadeamento que não faz parte do pool. Portanto, não estamos comprometendo os threads do pool que permanecem livres para atender a outras solicitações.

Darin Dimitrov
fonte
1
Muito detalhado, obrigado! Vamos supor que, temos 4 Tasks assíncronas ( System.Threading.Task) rodando dentro do IndexAsyncmétodo. Dentro dessas operações, estamos fazendo chamadas de banco de dados para um servidor. Então, todas elas são operações intensivas de E / S, certo? Nesse caso, criamos 4 threads separados (ou obtemos 4 threads separados do pool de threads)? Supondo que eu tenha uma máquina com vários núcleos, eles também serão executados em paralelo, certo?
tugberk
10
@tugberk, as chamadas de banco de dados são operações de E / S, mas tudo depende de como você as implementa. Se você usar uma chamada de bloqueio de banco de dados, como SqlCommand.ExecuteReadervocê está desperdiçando tudo, pois esta é uma chamada de bloqueio. Você está bloqueando o encadeamento no qual essa chamada é executada e, se esse encadeamento for um encadeamento do pool, é muito ruim. Você se beneficiará somente se você usar I / Ports O Conclusão: SqlCommand.BeginExecuteReader. Se você não usar o IOCP, não importa o que faça, não use controladores assíncronos, pois você causará mais danos do que benefícios no desempenho geral do seu aplicativo.
Darin Dimitrov
1
Bem, na maioria das vezes eu uso o código EF primeiro. Não tenho certeza se serve. Coloco uma amostra que mostra o que geralmente faço. Eu atualizei a pergunta, você pode dar uma olhada?
tugberk
3
@tugberk, você está executando-os em paralelo, portanto o tempo total de execução é menor comparado com se você executá-los sequencialmente. Mas, para executá-los, você usa threads de trabalho. Bem, na verdade, a EF é preguiçosa, então, quando você o faz, _context.Foonão está executando nada. Você está apenas construindo uma árvore de expressão. Seja extremamente cuidadoso com isso. A execução da consulta é adiada apenas quando você começa a enumerar sobre o conjunto de resultados. E se isso acontecer na visão, isso pode ser catastrófico para o desempenho. Para executar ansiosamente uma consulta EF, adicione .ToList()no final.
Darin Dimitrov
2
@tugberk, você precisará de uma ferramenta de teste de carga para simular vários usuários em paralelo no seu site e ver como ela se comporta sob carga pesada. O Mini Profiler não pode simular a carga no seu site. Isso pode ajudá-lo a ver e otimizar suas consultas do ADO.NET e a criar um perfil de uma única solicitação que é inútil quando você precisa ver como o site se comporta em uma situação do mundo real quando muitos usuários estão acessando.
Darin Dimitrov
49

Sim - todos os threads vêm do pool de threads. Seu aplicativo MVC já é multiencadeado, quando uma solicitação chega em um novo encadeamento será retirado do pool e usado para atender à solicitação. Esse encadeamento será 'bloqueado' (de outras solicitações) até que a solicitação seja totalmente atendida e concluída. Se não houver um encadeamento disponível no pool, a solicitação terá que esperar até que um esteja disponível.

Se você tiver controladores assíncronos, eles ainda receberão um encadeamento do pool, mas enquanto atendem à solicitação, eles podem desistir do encadeamento, enquanto aguardam que algo aconteça (e esse encadeamento pode ser fornecido para outra solicitação) e quando a solicitação original precisar de um encadeamento novamente ele recebe um da piscina.

A diferença é que, se você tiver muitas solicitações de execução longa (em que o encadeamento aguarda uma resposta de alguma coisa), poderá ficar sem encadeamentos do pool para atender a solicitações básicas. Se você possui controladores assíncronos, não possui mais threads, mas os threads que estão aguardando são retornados ao pool e podem atender a outras solicitações.

Um exemplo da vida quase real ... Pense nisso como entrar em um ônibus, há cinco pessoas esperando para entrar, a primeira entra, paga e senta (o motorista atendeu a sua solicitação), você continua (o motorista está atendendo seu pedido), mas você não consegue encontrar seu dinheiro; enquanto você mexe nos bolsos, o motorista desiste de você e coloca as próximas duas pessoas (atendendo a seus pedidos); quando você encontra seu dinheiro, o motorista começa a lidar com você novamente (concluindo seu pedido) - a quinta pessoa tem que esperar até você terminou, mas a terceira e quarta pessoas foram servidas enquanto você estava no meio do caminho. Isso significa que o motorista é o único fio da piscina e os passageiros são os pedidos. Era muito complicado escrever como funcionaria se houvesse dois drivers, mas você pode imaginar ...

Sem um controlador assíncrono, os passageiros atrás de você teriam que esperar muito tempo enquanto procurava seu dinheiro, enquanto o motorista do ônibus não faria nenhum trabalho.

Portanto, a conclusão é que, se muitas pessoas não souberem onde está seu dinheiro (por exemplo, exigir muito tempo para responder a algo que o motorista pediu), os controladores assíncronos podem muito bem ajudar no processamento de solicitações, acelerando o processo de alguns. Sem um controlador aysnc, todo mundo espera até que a pessoa na frente seja completamente tratada. MAS não esqueça que no MVC você tem muitos drivers de barramento em um único barramento, portanto, a assíncrona não é uma opção automática.

K. Bob
fonte
8
Muito boa analogia. Obrigado.
Pittsburgh DBA
Gostei da descrição. Obrigado
Omer Cansizoglu
Excelente maneira de explicar it.thank você,
Anand Vyas
Sua resposta em combinação com a resposta de Darin resume todo o mecanismo por trás dos controladores assíncronos, o que é e, mais importante, o que não é!
Nirman
Essa é uma boa analogia, mas minha única pergunta é a seguinte: o cara que mexe nos bolsos da sua analogia seria algum tipo de trabalho / processamento em nosso aplicativo ... então, quais processos funcionam depois que liberamos o thread? Certamente é outra discussão? Então, o que ganhamos aqui?
Tomuke
10

Existem dois conceitos em jogo aqui. Primeiro, podemos fazer com que nosso código seja executado em paralelo para executar mais rapidamente ou agendar código em outro encadeamento para evitar que o usuário espere. O exemplo que você teve

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

pertence à segunda categoria. O usuário receberá uma resposta mais rápida, mas a carga de trabalho total no servidor é maior porque precisa fazer o mesmo trabalho + lidar com o encadeamento.

Outro exemplo disso seria:

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Make async web request to twitter with WebClient.DownloadString()
    });

    Task.Factory.StartNew(() => { 
        //Make async web request to facebook with WebClient.DownloadString()
    });


    //wait for both to be ready and merge the results

    return View();
}

Como as solicitações são executadas em paralelo, o usuário não precisará esperar o tempo que for feito em série. Mas você deve perceber que usamos mais recursos aqui do que se rodássemos em série, porque rodamos o código em muitos threads enquanto ainda estamos esperando.

Isso é perfeitamente adequado em um cenário de cliente. E é bastante comum envolver o código síncrono de execução longa em uma nova tarefa (executá-lo em outro encadeamento) e também manter a interface do usuário responsiva ou paralela para torná-la mais rápida. Um thread ainda é usado por toda a duração. Em um servidor com carga alta, isso pode sair pela culatra, porque você realmente usa mais recursos. É sobre isso que as pessoas o alertaram

Os controladores assíncronos no MVC têm outro objetivo. O objetivo aqui é evitar que as seções de roscas fiquem sem fazer nada (o que pode prejudicar a escalabilidade). Realmente importa apenas se as APIs que você está chamando têm métodos assíncronos. Como WebClient.DowloadStringAsync ().

O ponto é que você pode permitir que seu encadeamento seja retornado para lidar com novas solicitações até que a solicitação da web seja concluída, onde será chamado o retorno de chamada que obtém o mesmo ou um novo encadeamento e finaliza a solicitação.

Espero que você entenda a diferença entre assíncrono e paralelo. Pense no código paralelo como o código em que seu encadeamento fica e aguarde o resultado. Embora o código assíncrono seja o código em que você será notificado quando o código for concluído e poderá voltar a trabalhar nele, enquanto isso, o encadeamento pode executar outro trabalho.

Mikael Eliasson
fonte
6

Os aplicativos podem escalar melhor se as operações forem executadas de forma assíncrona, mas apenas se houver recursos disponíveis para atender as operações adicionais .

As operações assíncronas garantem que você nunca bloqueie uma ação porque uma existente está em andamento. O ASP.NET possui um modelo assíncrono que permite que várias solicitações sejam executadas lado a lado. Seria possível enfileirar as solicitações e processá-las FIFO, mas isso não seria escalável quando você tem centenas de solicitações na fila e cada solicitação leva 100ms para processar.

Se você tem um grande volume de tráfego, você pode ser melhor fora de não realizar suas consultas de forma assíncrona, como pode não haver recursos adicionais para atender as solicitações . Se não houver recursos sobressalentes, suas solicitações serão forçadas a ficar na fila, levar uma falha exponencialmente mais longa ou definitiva; nesse caso, a sobrecarga assíncrona (operações de mutex e mutação de contexto) não está fornecendo nada.

No que diz respeito ao ASP.NET, você não tem escolha - ele usa um modelo assíncrono, porque é isso que faz sentido para o modelo servidor-cliente. Se você estivesse escrevendo internamente seu próprio código que usa um padrão assíncrono para tentar escalar melhor, a menos que esteja tentando gerenciar um recurso que é compartilhado entre todas as solicitações, você não verá melhorias, pois elas já estão agrupadas em um processo assíncrono que não bloqueia mais nada.

Por fim, tudo é subjetivo até que você realmente veja o que está causando um gargalo no seu sistema. Às vezes, é óbvio onde um padrão assíncrono ajudará (impedindo o bloqueio de recursos na fila). Por fim, apenas a medição e a análise de um sistema podem indicar onde você pode obter eficiências.

Editar:

No seu exemplo, a Task.Factory.StartNewchamada enfileirará uma operação no pool de threads .NET. A natureza dos encadeamentos do Conjunto de Encadeamentos deve ser reutilizada (para evitar o custo de criar / destruir muitos encadeamentos). Depois que a operação é concluída, o encadeamento é liberado de volta ao pool para ser reutilizado por outra solicitação (o Garbage Collector na verdade não se envolve, a menos que você tenha criado alguns objetos em suas operações. Nesse caso, eles são coletados normalmente escopo).

No que diz respeito ao ASP.NET, não há operação especial aqui. A solicitação do ASP.NET é concluída sem considerar a tarefa assíncrona. A única preocupação pode ser se o pool de threads estiver saturado (por exemplo, não há threads disponíveis para atender à solicitação no momento e as configurações do pool não permitirem a criação de mais threads); nesse caso, a solicitação será bloqueada aguardando o início do tarefa até que um encadeamento de pool fique disponível.

Paul Turner
fonte
Obrigado! Depois de ler sua resposta, editei a pergunta com um exemplo de código. Você pode dar uma olhada?
tugberk
Você tem uma frase mágica para mim: a Task.Factory.StartNewchamada enfileirará uma operação no pool de threads .NET. . Nesse contexto, qual é o correto aqui: 1-) Ele cria um novo encadeamento e, quando concluído, o encadeamento volta ao conjunto de encadeamentos e espera que seja reutilizado novamente. 2-) Ele obtém um thread do pool de threads e esse thread volta ao threadpool e espera que seja reutilizado novamente. 3-) Adota a abordagem mais eficiente e pode fazer qualquer uma delas.
tugberk
1
O conjunto de encadeamentos cria encadeamentos conforme necessário e recicla encadeamentos quando não estão sendo usados. Seu comportamento exato variou entre as versões do CLR. Você pode encontrar informações específicas sobre isso aqui msdn.microsoft.com/en-us/library/0ka9477y.aspx
Paul Turner
Começa a se formar na minha mente agora. Então, o CLR possui o pool de threads, certo? Por exemplo, um aplicativo WPF também possui uma noção de pool de encadeamentos e também lida com esse pool.
tugberk
1
O Pool de Threads é algo próprio no CLR. Outros componentes que estão "cientes" do pool estão indicando que eles usam threads do Pool de Threads (quando apropriado), em vez de criar e destruir seus próprios. Criar ou destruir um encadeamento é uma operação relativamente cara, portanto, usar o pool é um grande ganho de eficiência em operações de execução curta.
Paul Turner
2

Sim, eles usam um thread do pool de threads. Na verdade, existe um guia excelente do MSDN que abordará todas as suas perguntas e muito mais. Eu achei que era bastante útil no passado. Confira!

http://msdn.microsoft.com/en-us/library/ee728598.aspx

Enquanto isso, os comentários + sugestões que você ouve sobre código assíncrono devem ser tomados com um pouco de sal. Para iniciantes, apenas fazer algo assíncrono não necessariamente aumenta sua escala e, em alguns casos, pode piorar a escala de seu aplicativo. O outro comentário que você publicou sobre "um enorme volume de tráfego ..." também está correto apenas em determinados contextos. Realmente depende do que suas operações estão fazendo e de como elas interagem com outras partes do sistema.

Em resumo, muitas pessoas têm muitas opiniões sobre assíncrono, mas elas podem não estar corretas fora de contexto. Eu diria que se concentre nos seus problemas exatos e faça testes básicos de desempenho para ver o que os controladores assíncronos etc. realmente lidam com o seu aplicativo.

AR
fonte
Eu li esse documento talvez centenas de vezes e ainda tenho muita confusão (talvez o problema seja eu, quem sabe). Quando você olha em volta, vê tantos comentários inconsistentes sobre assincronia no ASP.NET MVC quanto pode ver na minha pergunta.
tugberk
para a última frase: dentro de uma ação do controlador, eu estava consultando um banco de dados 5 vezes separadamente (eu precisava) e tudo estava levando aproximadamente 400 ms. Em seguida, implementei o AsyncController e os executei em paralelo. O tempo de resposta foi reduzido drasticamente para aprox. 200 ms. Mas não tenho idéia de quantos threads ele cria, o que acontece com esses threads depois que termino com eles, GCvem e os limpa logo após o término, para que meu aplicativo não tenha vazamento de memória, etc. Alguma idéia dessa parte.
tugberk
anexar um depurador e descobrir.
AR
0

A primeira coisa que não é o MVC, mas o IIS que mantém o pool de threads. Portanto, qualquer solicitação que chega ao aplicativo MVC ou ASP.NET é atendida a partir de threads mantidos no pool de threads. Somente ao criar o aplicativo Asynch, ele invoca essa ação em um thread diferente e libera o thread imediatamente para que outras solicitações possam ser atendidas.

Expliquei o mesmo com um vídeo detalhado ( http://www.youtube.com/watch?v=wvg13n5V0V0/ "Controladores MVC assíncronos e falta de thread") que mostra como a falta de thread acontece no MVC e como é minimizada usando o MVC Controladores assíncronos. Também medi as filas de solicitação usando perfmon, para que você possa ver como as filas de solicitação são reduzidas para a sincronização MVC e como é pior para as operações de sincronização.

Shivprasad Koirala
fonte