Por que HttpContext.Current é nulo após esperar?

91

Eu tenho o seguinte código WebAPI de teste, não uso WebAPI na produção, mas fiz isso devido a uma discussão que tive sobre esta questão: Pergunta WebAPI Async

De qualquer forma, aqui está o método WebAPI ofensivo:

public async Task<string> Get(int id)
{
    var x = HttpContext.Current;
    if (x == null)
    {
        // not thrown
        throw new ArgumentException("HttpContext.Current is null");
    }

    await Task.Run(() => { Task.Delay(500); id = 3; });

    x = HttpContext.Current;
    if (x == null)
    {
        // thrown
        throw new ArgumentException("HttpContext.Current is null");
    }

    return "value";
}

Eu acreditava aqui que a segunda exceção é esperada porque, quando o for awaitconcluído, provavelmente será em um segmento diferente, onde HttpContext.Currentuma variável estática do segmento não resolverá mais para o valor apropriado. Agora, com base no contexto de sincronização, ele poderia realmente ser forçado a voltar para o mesmo encadeamento após o await, mas não estou fazendo nada sofisticado em meu teste. Este é apenas um uso simples e ingênuo de await.

Em comentários em outra questão foi-me dito que HttpContext.Currentdeveria resolver depois de esperar. Há até outro comentário sobre esta questão indicando o mesmo. Então, o que é verdade? Deve resolver? Eu acho que não, mas eu quero uma resposta confiável sobre isso porque asynce awaité novo o suficiente para não conseguir encontrar nada definitivo.

TL; DR: É HttpContext.Currentpotencialmente nullapós um await?

Welegan
fonte
3
Sua pergunta não está clara - você disse o que esperava que acontecesse e os comentários indicam que é isso que está acontecendo ... então, o que está confundindo você?
Jon Skeet de
@ user2674389, isso é enganoso. É AspNetSynchronizationContextisso que cuida HttpContext, não await. Além disso, o retorno de chamada de continuação para awaitpode (e provavelmente irá) ocorrer em um thread diferente para o modelo de execução de API da Web.
noseratio
editado para fazer uma pergunta sucinta
vindo em
1
@JoepBeusenberg Criar assemblies separados que funcionam apenas quando são chamados de um assembly que está sendo executado dentro do contexto de uma solicitação HTTP de uma pilha da web em particular parece que pode tornar o teste, a manutenção e a reutilização um desafio.
Darrel Miller
1
@DarrelMiller Muito pelo contrário. Separei a lógica de negócios do projeto da web real. Usando injeção de dependência, posso adicionar uma biblioteca compatível com webapi no topo da lógica de negócios. Mas essa biblioteca é interrompida quando a lógica de negócios é concluída em .ConfigureAwait(false)algum ponto da linha. Não há solicitação ou controlador explicitamente transmitido pela camada de negócios, uma vez que esta não tem conhecimento da web. Isso é útil, por exemplo, para um módulo de registro que pode injetar os detalhes da solicitação quando a lógica de negócios grava um genérico TraceInformation.
Joep Beusenberg

Respostas:

150

Certifique-se de estar escrevendo um aplicativo ASP.NET 4.5 e visando 4.5. asynce awaittêm comportamento indefinido no ASP.NET, a menos que você esteja executando no 4.5 e esteja usando o novo contexto de sincronização "amigável para tarefas".

Em particular, isso significa que você deve:

  • Definir httpRuntime.targetFrameworkcomo 4.5, ou
  • Em seu appSettings, defina aspnet:UseTaskFriendlySynchronizationContextcomo true.

Mais informações estão disponíveis aqui .

Stephen Cleary
fonte
2
Acabei de criar um novo projeto ASP.NET 4.5 WebAPI, copiei / colei seu código e fiz um teste. Funcionou perfeitamente para mim (nenhuma exceção foi lançada). Por favor, re-verifique se está em execução no e direcionamento 4.5.
Stephen Cleary
3
Eu tenho estrutura de destino: .NET Framework 4.5 definido. Não sei o que dizer, é definitivamente nulo na minha máquina local.
welegan
24
o <httpRuntime targetFramework="4.5" />é o que resolveu, obrigado por esclarecer.
welegan
1
@Vince: 4.5.1 deve funcionar bem. Não tenho certeza se você deve / poderia definir targetFrameworkcomo 4.5.1 ou 4.5, mas o assíncrono em 4.5.1 deve funcionar bem.
Stephen Cleary
1
E se você escreveu seu próprio gerenciador gerenciado? Eu continuo criando HttpContext.Current = null lá mesmo depois de adicionar esses itens no web.config.
Brain2000,
30

Como @StephenCleary corretamente apontou, você precisa disso em seu web.config:

<httpRuntime targetFramework="4.5" />

Quando eu estava solucionando isso pela primeira vez, fiz uma pesquisa ampla de solução para o acima, confirmei que estava presente em todos os meus projetos da web e rapidamente descartei-o como o culpado. Por fim, ocorreu-me olhar para os resultados da pesquisa em todo o contexto:

<!--
  For a description of web.config changes for .NET 4.5 see http://go.microsoft.com/fwlink/?LinkId=235367.

  The following attributes can be set on the <httpRuntime> tag.
    <system.Web>
      <httpRuntime targetFramework="4.5" />
    </system.Web>
-->

Doh.

Lição: Se você atualizar um projeto da web para 4.5, ainda precisará fazer essa configuração manualmente.

Todd Menier
fonte
22
Outra pegadinha é que isso é diferente de <compilation targetFramework "4.5" />
Andrew
3

Meu teste está com falha ou há algum elemento web.config que estou perdendo aqui que faria HttpContext.Current resolver corretamente após um await?

Seu teste não é falho e HttpContext.Current não deve ser nulo após o await porque na API Web ASP.NET quando você espera, isso garantirá que o código que segue esse await seja transmitido ao HttpContext correto que estava presente antes do await.

Darin Dimitrov
fonte
Tem certeza sobre o mesmo segmento de continuação para WebAPI? Lidei com o caso em que era um tópico diferente.
noseratio
4
O ASP.NET continuará em qualquer thread do pool de threads, mas com o contexto de solicitação correto.
Stephen Cleary
2
Sim, você está certo, o tópico pode não ser o mesmo, mas HttpContext.Current será o mesmo de antes do await. Eu atualizei minha pergunta.
Darin Dimitrov
4
HttpContext.Current é nulo após um await em meu código e estou visando .net 4.6.1.
Triynko
1
para mim HttpContext.Current é nulo antes de uma função de espera
JobaDiniz,
3

Eu encontrei esse problema recentemente. Como Stephen apontou, não definir explicitamente a estrutura de destino pode gerar esse problema.

No meu caso, nossa API da Web foi migrada para a versão 4.6.2, mas a estrutura de destino do tempo de execução nunca foi especificada na configuração da web, então basicamente isso estava faltando na tag <system.web>:

Se você tiver dúvidas sobre a versão do framework que está executando, isso pode ajudar: Adicione a seguinte linha em qualquer um dos seus métodos de API da Web e defina um ponto de interrupção para verificar que tipo está carregado atualmente em tempo de execução e verifique se não é uma implementação Legacy:

Você deve ver isto (AspNetSynchronizationContext):

insira a descrição da imagem aqui

Em vez de LegazyAspNetSynchronizationContext (que foi o que vi antes de adicionar a estrutura de destino):

insira a descrição da imagem aqui

Se você acessar o código-fonte ( https://referencesource.microsoft.com/#system.web/LegacyAspNetSynchronizationContext.cs ), verá que a implementação herdada dessa interface carece de suporte assíncrono.

insira a descrição da imagem aqui

Passei muito tempo tentando encontrar a origem do problema e a resposta de Stephen ajudou muito. Espero que esta resposta forneça mais informações sobre o problema.

Abarrenechea
fonte