Perché HttpContext.Current è null dopo await?


89

Ho il seguente codice WebAPI di prova, non utilizzo WebAPI in produzione ma l'ho fatto a causa di una discussione che ho avuto su questa domanda: WebAPI Async question

Ad ogni modo, ecco il metodo WebAPI incriminato:

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";
}

Finora avevo creduto che la seconda eccezione fosse prevista perché quando il awaitcompletamento sarà probabilmente su un thread diverso dove HttpContext.Currentcome variabile thread-static non si risolverà più al valore appropriato. Ora, in base al contesto di sincronizzazione, potrebbe effettivamente essere costretto a tornare allo stesso thread dopo l'attesa, ma non sto facendo nulla di stravagante nel mio test. Questo è solo un uso semplice e ingenuo di await.

Nei commenti in un'altra domanda mi è stato detto che HttpContext.Currentdovrebbe risolversi dopo un'attesa. C'è anche un altro commento su questa domanda che indica lo stesso. Allora cosa è vero? Dovrebbe risolversi? Penso di no, ma voglio una risposta autorevole su questo perché asynced awaitè abbastanza nuovo da non riuscire a trovare nulla di definitivo.

TL; DR: è HttpContext.Currentpotenzialmente nulldopo un await?


3
La tua domanda non è chiara: hai detto quello che ti aspettavi che accadesse ei commenti indicano che è quello che sta succedendo ... quindi cosa ti confonde?
Jon Skeet

@ user2674389, questo è fuorviante. È AspNetSynchronizationContextquello che si prende cura di HttpContext, no await. Inoltre, il callback di continuazione per awaitmaggio (e molto probabilmente accadrà) si verifica su un thread diverso per il modello di esecuzione dell'API Web.
noseratio

modificato per porre una domanda succinta
welegan

1
@JoepBeusenberg La creazione di assembly separati che funzionano solo quando vengono chiamati da un assembly in esecuzione nel contesto di una richiesta HTTP di un particolare stack web sembra che potrebbe rendere il test, la manutenzione e il riutilizzo una sfida.
Darrel Miller

1
@DarrelMiller Al contrario. Ho separato la logica aziendale dal progetto web vero e proprio. Utilizzando l'inserimento delle dipendenze posso aggiungere una libreria compatibile con webapi in cima alla logica di business. Ma questa libreria si rompe quando la logica di business ha funzionato da .ConfigureAwait(false)qualche parte lungo la linea. Non ci sono richieste o controller esplicitamente passati attraverso il livello aziendale, poiché quello non è a conoscenza del web. Ciò è utile ad esempio per un modulo di registrazione che può inserire i dettagli della richiesta quando la logica aziendale scrive un generico TraceInformation.
Joep Beusenberg

Risposte:


148

Assicurati di scrivere un'applicazione ASP.NET 4.5 e di avere come target 4.5. asynce awaithanno un comportamento indefinito su ASP.NET a meno che non si stia eseguendo su 4.5 e si utilizzi il nuovo contesto di sincronizzazione "facile da usare".

In particolare, questo significa che devi:

  • Impostare httpRuntime.targetFrameworksu 4.5, o
  • Nel tuo appSettings, impostato aspnet:UseTaskFriendlySynchronizationContextsu true.

Maggiori informazioni sono disponibili qui .


2
Ho appena creato un nuovo progetto ASP.NET 4.5 WebAPI, ho copiato / incollato il codice e ho fatto un test. Ha funzionato perfettamente per me (nessuna eccezione è stata lanciata). Ricontrolla di essere in esecuzione e di avere come target 4.5.
Stephen Cleary

3
Ho Target framework: .NET Framework 4.5 impostato. Non so cosa dirti, è decisamente nullo sulla mia macchina locale.
welegan

24
la <httpRuntime targetFramework="4.5" />è quello risolto, grazie per chiarire.
welegan

1
@Vince: 4.5.1 dovrebbe funzionare bene. Non sono sicuro se dovresti / potresti impostare targetFrameworksu 4.5.1 o 4.5, ma l'asincronia su 4.5.1 dovrebbe funzionare bene.
Stephen Cleary,

1
E se scrivessi il tuo gestore gestito? Continuo a inventare HttpContext.Current = null anche dopo aver aggiunto questi elementi in web.config.
Brain2000

28

Come ha sottolineato correttamente @StephenCleary, ne hai bisogno nel tuo web.config:

<httpRuntime targetFramework="4.5" />

Quando stavo risolvendo questo problema per la prima volta, ho effettuato una ricerca a livello di soluzione per quanto sopra, ho confermato che era presente in tutti i miei progetti web e l'ho rapidamente liquidato come colpevole. Alla fine mi è venuto in mente di guardare quei risultati di ricerca nel contesto completo:

<!--
  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.

Lezione: se si aggiorna un progetto Web a 4.5, è comunque necessario impostare manualmente tale impostazione.


22
Un altro problema è che questo è diverso da <compilation targetFramework "4.5" />
Andrew

3

Il mio test è difettoso o c'è qualche elemento web.config che mi manca qui che farebbe risolvere correttamente HttpContext.Current dopo un'attesa?

Il tuo test non è difettoso e HttpContext.Current non dovrebbe essere nullo dopo l'attesa perché nell'API Web ASP.NET quando attendi, questo assicurerà che il codice che segue questo attese venga passato l'HttpContext corretto che era presente prima dell'attesa.


Sei sicuro dello stesso thread di continuazione per WebAPI? Ho affrontato il caso in cui era un filo diverso.
noseratio

4
ASP.NET riprenderà su qualsiasi thread del pool di thread, ma con il contesto di richiesta corretto.
Stephen Cleary

2
Sì, hai ragione, il thread potrebbe non essere lo stesso ma HttpContext.Current sarà lo stesso di prima del await. Ho aggiornato la mia domanda.
Darin Dimitrov

4
HttpContext.Current è null dopo un'attesa nel mio codice e sto mirando a .net 4.6.1.
Triynko

1
per me HttpContext.Current è nullo prima di una funzione di attesa
JobaDiniz

2

Mi sono imbattuto in questo problema di recente. Come ha sottolineato Stephen, non impostare esplicitamente il framework di destinazione può generare questo problema.

Nel mio caso, la nostra API Web è stata migrata alla versione 4.6.2 ma il framework di destinazione del runtime non è mai stato specificato nella configurazione web, quindi sostanzialmente questo mancava all'interno del tag <system.web>:

In caso di dubbi sulla versione del framework in esecuzione, ciò può essere d'aiuto: aggiungere la seguente riga su uno dei metodi dell'API Web e impostare un punto di interruzione per verificare quale tipo è attualmente caricato in fase di esecuzione e verificare che non sia un'implementazione Legacy:

Dovresti vedere questo (AspNetSynchronizationContext):

inserisci qui la descrizione dell'immagine

Invece di LegazyAspNetSynchronizationContext (che era quello che ho visto prima di aggiungere il framework di destinazione):

inserisci qui la descrizione dell'immagine

Se vai al codice sorgente ( https://referencesource.microsoft.com/#system.web/LegacyAspNetSynchronizationContext.cs ) vedrai che l'implementazione Legacy di questa interfaccia manca di supporto asincrono.

inserisci qui la descrizione dell'immagine

Ho passato molto tempo a cercare di trovare la fonte del problema e la risposta di Stephen è stata di grande aiuto. Spero che questa risposta fornisca ulteriori informazioni sul problema.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.