Pourquoi HttpContext.Current est-il nul après attendre?

90

J'ai le code de test WebAPI suivant, je n'utilise pas WebAPI en production mais j'ai fait cela à cause d'une discussion que j'ai eue sur cette question: Question WebAPI Async

Quoi qu'il en soit, voici la méthode WebAPI incriminée:

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

J'avais ici cru que la deuxième exception est attendue parce que lorsque le sera awaitterminé, ce sera probablement sur un thread différent où, en HttpContext.Currenttant que variable de thread statique, ne sera plus résolu à la valeur appropriée. Maintenant, en fonction du contexte de synchronisation, il pourrait en fait être forcé de revenir au même thread après l'attente, mais je ne fais rien d'extraordinaire dans mon test. Ceci est juste une utilisation simple et naïve de await.

Dans les commentaires d'une autre question, on m'a dit que cela HttpContext.Currentdevrait être résolu après une attente. Il y a même un autre commentaire sur cette question indiquant la même chose. Alors qu'est-ce qui est vrai? Doit-il se résoudre? Je pense que non, mais je veux une réponse faisant autorité à ce sujet parce que asyncet awaitest suffisamment nouveau pour que je ne trouve rien de définitif.

TL; DR: Est-ce HttpContext.Currentpotentiellement nullaprès un await?

Welegan
la source
3
Votre question ne sait pas - vous avez dit ce que vous vous attendiez, et les commentaires indiquent que c'est ce qui se passe ... Alors qu'est - ce que vous confondre?
Jon Skeet
@ user2674389, c'est trompeur. C'est AspNetSynchronizationContextça qui s'occupe HttpContext, non await. De plus, le rappel de continuation pour awaitpeut (et probablement se produira) sur un thread différent pour le modèle d'exécution de l'API Web.
noseratio
édité pour poser une question succincte
welegan
1
@JoepBeusenberg La création d'assemblys séparés qui ne fonctionnent que lorsqu'ils sont appelés à partir d'un assembly qui s'exécute dans le contexte d'une requête HTTP d'une pile Web particulière semble rendre le test, la maintenance et la réutilisation un défi.
Darrel Miller
1
@DarrelMiller Bien au contraire. J'ai séparé la logique métier du projet Web proprement dit. En utilisant l'injection de dépendances, je peux ajouter une bibliothèque compatible webapi en plus de la logique métier. Mais cette bibliothèque se rompt lorsque la logique métier a fait .ConfigureAwait(false)quelque chose sur toute la ligne. Il n'y a pas de demande ou de contrôleur explicitement transmis via la couche métier, car celui-ci n'est pas compatible Web. Ceci est utile par exemple pour un module de journalisation qui peut injecter les détails de la requête lorsque la logique métier écrit un générique TraceInformation.
Joep Beusenberg

Réponses:

148

Veuillez vous assurer que vous écrivez une application ASP.NET 4.5 et ciblez 4.5. asyncet awaitont un comportement indéfini sur ASP.NET, sauf si vous exécutez sur 4.5 et utilisez le nouveau contexte de synchronisation "convivial pour les tâches".

En particulier, cela signifie que vous devez soit:

  • Définir httpRuntime.targetFrameworksur 4.5, ou
  • Dans votre appSettings , réglez aspnet:UseTaskFriendlySynchronizationContextsur true.

Plus d'informations sont disponibles ici .

Stephen Cleary
la source
2
Je viens de créer un nouveau projet WebAPI ASP.NET 4.5, de copier / coller votre code et de faire un test. Cela a parfaitement fonctionné pour moi (aucune exception n'a été lancée). Veuillez vérifier à nouveau que vous utilisez et ciblez 4.5.
Stephen Cleary
3
J'ai le framework cible: .NET Framework 4.5 défini. Je ne sais pas quoi vous dire, c'est définitivement nul sur ma machine locale.
welegan
24
l' <httpRuntime targetFramework="4.5" />est ce qui a résolu le problème, merci de préciser.
welegan
1
@Vince: 4.5.1 devrait fonctionner correctement. Je ne sais pas si vous devriez / pourriez définir targetFrameworksur 4.5.1 ou 4.5, mais async sur 4.5.1 devrait fonctionner correctement.
Stephen Cleary
1
Et si vous écriviez votre propre gestionnaire géré? Je continue à proposer HttpContext.Current = null même après avoir ajouté ces éléments dans le web.config.
Brain2000
28

Comme @StephenCleary l'a correctement souligné, vous en avez besoin dans votre web.config:

<httpRuntime targetFramework="4.5" />

Lorsque j'ai résolu ce problème pour la première fois, j'ai effectué une recherche à l'échelle de la solution pour ce qui précède, confirmé qu'il était présent dans tous mes projets Web et l'ai rapidement rejeté comme coupable. Finalement, il m'est venu à l'esprit de regarder ces résultats de recherche dans leur contexte complet:

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

Leçon: Si vous mettez à niveau un projet Web vers la version 4.5, vous devez toujours mettre ce paramètre en place manuellement.

Todd Menier
la source
22
Un autre problème est que c'est différent de <compilation targetFramework "4.5" />
Andrew
3

Mon test est-il défectueux ou y a-t-il un élément web.config qui me manque ici et qui permettrait à HttpContext.Current de se résoudre correctement après une attente?

Votre test n'est pas défectueux et HttpContext.Current ne doit pas être nul après l'attente car dans l'API Web ASP.NET lorsque vous attendez, cela garantira que le code qui suit cette attente reçoit le HttpContext correct qui était présent avant l'attente.

Darin Dimitrov
la source
Êtes-vous sûr du même fil de continuation pour WebAPI? J'ai traité le cas où c'était un fil différent.
noseratio
4
ASP.NET reprendra sur n'importe quel thread de pool de threads, mais avec le contexte de demande correct.
Stephen Cleary
2
Oui, vous avez raison, le thread n'est peut-être pas le même mais HttpContext.Current sera le même qu'avant l'attente. J'ai mis à jour ma question.
Darin Dimitrov
4
HttpContext.Current est nul après une attente dans mon code, et je cible .net 4.6.1.
Triynko
1
pour moi HttpContext.Current est nul avant une fonction d'attente
JobaDiniz
2

J'ai récemment rencontré ce problème. Comme Stephen l'a souligné, ne pas définir explicitement le cadre cible peut générer ce problème.

Dans mon cas, notre API Web a été migrée vers la version 4.6.2 mais le framework cible d'exécution n'a jamais été spécifié dans la configuration Web, donc fondamentalement, cela manquait dans la balise <system.web>:

Si vous avez des doutes sur la version du framework que vous exécutez, cela peut vous aider: Ajoutez la ligne suivante sur l'une de vos méthodes d'API Web et définissez un point d'arrêt pour vérifier le type actuellement chargé lors de l'exécution et vérifiez qu'il ne s'agit pas d'une implémentation héritée:

Vous devriez voir ceci (AspNetSynchronizationContext):

entrez la description de l'image ici

Au lieu de LegazyAspNetSynchronizationContext (ce que j'ai vu avant d'ajouter le framework cible):

entrez la description de l'image ici

Si vous accédez au code source ( https://referencesource.microsoft.com/#system.web/LegacyAspNetSynchronizationContext.cs ), vous verrez que l'implémentation héritée de cette interface manque de prise en charge asynchrone.

entrez la description de l'image ici

J'ai passé beaucoup de temps à essayer de trouver la source du problème et la réponse de Stephen m'a beaucoup aidé. J'espère que cette réponse fournit plus d'informations sur le problème.

abarrenechea
la source