Dlaczego HttpContext.Current ma wartość null po oczekiwaniu?

90

Mam następujący testowy kod WebAPI, nie używam WebAPI w środowisku produkcyjnym, ale zrobiłem to z powodu dyskusji, którą miałem na to pytanie: Pytanie Async WebAPI

Tak czy inaczej, oto obraźliwa metoda WebAPI:

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

Uważałem, że drugi wyjątek jest oczekiwany, ponieważ po awaitzakończeniu prawdopodobnie będzie w innym wątku, gdzie HttpContext.Currentzmienna statyczna wątku nie będzie już rozwiązywać się do odpowiedniej wartości. Teraz, w oparciu o kontekst synchronizacji, może być faktycznie zmuszony do powrotu do tego samego wątku po oczekiwaniu, ale nie robię nic nadzwyczajnego w moim teście. To tylko zwykłe, naiwne użycie await.

W komentarzach do innego pytania powiedziano mi, że HttpContext.Currentpowinno to ustąpić po odczekaniu. Jest nawet inny komentarz do tego pytania, który wskazuje na to samo. Więc co jest prawdą? Czy powinno rozwiązać? I nie myślę, ale chcę autorytatywną odpowiedź na to, bo asynci awaitjest na tyle nowy, że nie mogę znaleźć nic ostateczne.

TL; DR: Czy HttpContext.Currentpotencjalnie jest nullpo await?

Welegan
źródło
3
Twoje pytanie jest niejasne - powiedziałeś, czego się spodziewałeś, a komentarze wskazują, że tak właśnie się dzieje ... więc co cię dezorientuje?
Jon Skeet
@ user2674389, to jest mylące. To się AspNetSynchronizationContexttym zajmuje HttpContext, a nie await. Co więcej, wywołanie zwrotne kontynuacji dla awaitmoże (i najprawdopodobniej wystąpi) w innym wątku dla modelu wykonywania interfejsu API sieci Web.
noseratio
zredagowane, aby zadać zwięzłe pytanie
welegan
1
@JoepBeusenberg Tworzenie oddzielnych zestawów, które działają tylko wtedy, gdy są wywoływane z zestawu, który jest wykonywany w kontekście żądania HTTP jednego konkretnego stosu internetowego, wydaje się, że może to utrudniać testowanie, konserwację i ponowne użycie.
Darrel Miller,
1
@DarrelMiller Wręcz przeciwnie. Oddzieliłem logikę biznesową od rzeczywistego projektu internetowego. Korzystając z iniekcji zależności mogę dodać do logiki biznesowej bibliotekę obsługującą webapi. Ale ta biblioteka psuje się, gdy logika biznesowa .ConfigureAwait(false)gdzieś zaszła. Nie ma żądania ani kontrolera jawnie przekazanego przez warstwę biznesową, ponieważ ta warstwa nie obsługuje sieci. Jest to przydatne na przykład w przypadku modułu rejestrowania, który może wstrzyknąć szczegóły żądania, gdy logika biznesowa zapisuje rodzaj TraceInformation.
Joep Beusenberg

Odpowiedzi:

148

Upewnij się, że piszesz aplikację ASP.NET 4,5 i jest przeznaczona dla wersji 4,5. asynci awaitmają niezdefiniowane zachowanie w programie ASP.NET, chyba że używasz wersji 4.5 i nowego kontekstu synchronizacji „przyjaznego dla zadań”.

W szczególności oznacza to, że musisz:

  • Ustaw httpRuntime.targetFrameworkna 4.5lub
  • W twoim appSettings, ustaw aspnet:UseTaskFriendlySynchronizationContextna true.

Więcej informacji można znaleźć tutaj .

Stephen Cleary
źródło
2
Właśnie utworzyłem nowy projekt ASP.NET 4.5 WebAPI, skopiowałem / wkleiłem Twój kod i wykonałem test. U mnie zadziałało idealnie (żaden wyjątek nie został zgłoszony). Proszę ponownie sprawdzić, które są uruchomione na i kierowania 4.5.
Stephen Cleary
3
Mam docelowy framework: zestaw .NET Framework 4.5. Nie wiem, co ci powiedzieć, na moim lokalnym komputerze jest to zdecydowanie zerowe.
welegan
24
<httpRuntime targetFramework="4.5" />co go rozwiązać, dzięki za wyjaśnienie.
welegan
1
@Vince: 4.5.1 powinno działać dobrze. Nie jestem pewien, czy powinieneś / mógłbyś ustawić targetFrameworkna 4.5.1 lub 4.5, ale asynchronizacja w 4.5.1 powinna działać dobrze.
Stephen Cleary
1
A co jeśli napisałeś własny zarządzany program obsługi? Ciągle wymyślam HttpContext.Current = null, nawet po dodaniu tych elementów w web.config.
Brain2000
28

Jak poprawnie zauważył @StephenCleary, potrzebujesz tego w swoim web.config:

<httpRuntime targetFramework="4.5" />

Kiedy po raz pierwszy rozwiązywałem ten problem, przeszukałem powyższe rozwiązanie w całym rozwiązaniu, potwierdziłem, że występuje ono we wszystkich moich projektach internetowych i szybko odrzuciłem jako winowajcę. W końcu przyszło mi do głowy spojrzeć na te wyniki wyszukiwania w pełnym kontekście:

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

Lekcja: Jeśli zaktualizujesz projekt sieci Web do wersji 4.5, nadal musisz wprowadzić to ustawienie ręcznie.

Todd Menier
źródło
22
Innym problemem jest to, że różni się to od <compilation targetFramework "4.5" />
Andrew
3

Czy mój test jest błędny, czy brakuje mi jakiegoś elementu web.config, który spowodowałby poprawne rozwiązanie HttpContext.Current po oczekiwaniu?

Twój test nie jest wadliwy, a HttpContext.Current nie powinien mieć wartości null po oczekiwaniu, ponieważ w ASP.NET Web API, gdy czekasz, zapewni to, że kod następujący po tym await zostanie przekazany do prawidłowego HttpContext, który był obecny przed await.

Darin Dimitrov
źródło
Czy na pewno ten sam wątek kontynuacyjny dla WebAPI? Zajmowałem się przypadkiem, w którym był to inny wątek.
noseratio
4
ASP.NET zostanie wznowiony w dowolnym wątku puli wątków, ale z poprawnym kontekstem żądania.
Stephen Cleary
2
Tak, masz rację, wątek może nie być taki sam, ale HttpContext.Current będzie taki sam jak przed await. Zaktualizowałem moje pytanie.
Darin Dimitrov
4
HttpContext.Current ma wartość null po oczekiwaniu w moim kodzie, a ja kieruję na .net 4.6.1.
Triynko,
1
dla mnie HttpContext.Current jest zerowy przed funkcją
await
2

Niedawno natknąłem się na ten problem. Jak zauważył Stephen, brak jednoznacznego określenia ram docelowych może spowodować ten problem.

W moim przypadku nasz internetowy interfejs API został zmigrowany do wersji 4.6.2, ale docelowa struktura środowiska wykonawczego nigdy nie została określona w konfiguracji internetowej, więc zasadniczo brakowało tego w tagu <system.web>:

Jeśli masz wątpliwości co do używanej wersji platformy, może to pomóc: Dodaj następujący wiersz do dowolnej metody interfejsu API sieci Web i ustaw punkt przerwania, aby sprawdzić, jaki typ jest aktualnie ładowany w czasie wykonywania i sprawdzić, czy nie jest to starsza implementacja:

Powinieneś zobaczyć to (AspNetSynchronizationContext):

wprowadź opis obrazu tutaj

Zamiast LegazyAspNetSynchronizationContext (co widziałem przed dodaniem platformy docelowej):

wprowadź opis obrazu tutaj

Jeśli przejdziesz do kodu źródłowego ( https://referencesource.microsoft.com/#system.web/LegacyAspNetSynchronizationContext.cs ), zobaczysz, że starszej implementacji tego interfejsu brakuje asynchronicznej obsługi.

wprowadź opis obrazu tutaj

Spędziłem dużo czasu próbując znaleźć źródło problemu, a odpowiedź Stephena bardzo pomogła. Mam nadzieję, że ta odpowiedź zawiera więcej informacji na temat problemu.

abarrenechea
źródło