¿Por qué HttpContext.Current es nulo después de await?

89

Tengo el siguiente código de prueba WebAPI, no uso WebAPI en producción, pero hice esto debido a una discusión que tuve sobre esta pregunta: Pregunta asíncrona de WebAPI

De todos modos, aquí está el 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";
}

Hasta ahora, había creído que se esperaba la segunda excepción porque cuando se awaitcomplete, es probable que esté en un hilo diferente donde, HttpContext.Currentcomo una variable estática de hilo, ya no se resolverá en el valor apropiado. Ahora, según el contexto de sincronización, en realidad podría verse obligado a volver al mismo hilo después de la espera, pero no estoy haciendo nada elegante en mi prueba. Este es un simple e ingenuo uso de await.

En los comentarios de otra pregunta me dijeron que HttpContext.Currentdebería resolverse después de una espera. Incluso hay otro comentario sobre esta pregunta que indica lo mismo. Entonces, ¿qué es verdad? ¿Debería resolverse? Creo que no, pero quiero una respuesta autorizada sobre esto porque asyncy awaites lo suficientemente nuevo como para no encontrar nada definitivo.

TL; DR: ¿Es HttpContext.Currentpotencialmente nulldespués de un await?

Welegan
fuente
3
Tu pregunta no es clara: has dicho lo que esperabas que sucediera y los comentarios indican que eso es lo que está sucediendo ... entonces, ¿qué te confunde?
Jon Skeet
@ user2674389, esto es engañoso. Es el AspNetSynchronizationContextque se ocupa HttpContext, no await. Además, la devolución de llamada de continuación para awaitpuede (y muy probablemente ocurrirá) en un hilo diferente para el modelo de ejecución de API web.
noseratio
editado para plantear una pregunta sucinta
welegan
1
@JoepBeusenberg La creación de ensamblados separados que solo funcionan cuando se los llama desde un ensamblado que se está ejecutando dentro del contexto de una solicitud HTTP de una pila web en particular, parece que podría hacer que las pruebas, el mantenimiento y la reutilización sean un desafío.
Darrel Miller
1
@DarrelMiller Todo lo contrario. He separado la lógica empresarial del proyecto web real. Usando la inyección de dependencia, puedo agregar una biblioteca compatible con webapi además de la lógica empresarial. Pero esta biblioteca se rompe cuando la lógica empresarial se ha desarrollado en .ConfigureAwait(false)algún momento. No hay una solicitud ni un controlador que se entregue explícitamente a través de la capa empresarial, ya que esa no es compatible con la web. Esto es útil, por ejemplo, para un módulo de registro que puede inyectar los detalles de la solicitud cuando la lógica empresarial escribe un genérico TraceInformation.
Joep Beusenberg

Respuestas:

148

Asegúrese de escribir una aplicación ASP.NET 4.5 y apuntar a 4.5. asyncy awaittienen un comportamiento indefinido en ASP.NET a menos que esté ejecutando en 4.5 y esté usando el nuevo contexto de sincronización "fácil de usar".

En particular, esto significa que debe:

  • Establecer httpRuntime.targetFrameworken 4.5, o
  • En su appSettings, establezca aspnet:UseTaskFriendlySynchronizationContexten true.

Más información está disponible aquí .

Stephen Cleary
fuente
2
Acabo de crear un nuevo proyecto ASP.NET 4.5 WebAPI, copié / pegué su código e hice una prueba. Funcionó perfectamente para mí (no se lanzó ninguna excepción). Vuelva a comprobar que se está ejecutando y apuntando a 4.5.
Stephen Cleary
3
Tengo el marco de destino: .NET Framework 4.5 configurado. No sé qué decirte, definitivamente es nulo en mi máquina local.
welegan
24
El <httpRuntime targetFramework="4.5" />es lo que lo solucionó, gracias por aclarar.
welegan
1
@Vince: 4.5.1 debería funcionar bien. No estoy seguro de si debería / podría establecer targetFrameworken 4.5.1 o 4.5, pero async en 4.5.1 debería funcionar bien.
Stephen Cleary
1
¿Y si escribiera su propio controlador administrado? Sigo apareciendo con HttpContext.Current = null allí incluso después de agregar estos elementos en web.config.
Brain2000
28

Como @StephenCleary señaló correctamente, necesita esto en su web.config:

<httpRuntime targetFramework="4.5" />

Cuando solucioné este problema por primera vez, hice una búsqueda en toda la solución de lo anterior, confirmé que estaba presente en todos mis proyectos web y lo descarté rápidamente como el culpable. Finalmente se me ocurrió mirar esos resultados de búsqueda en contexto 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.

Lección: Si actualiza un proyecto web a la versión 4.5, aún debe implementar esa configuración manualmente.

Todd Menier
fuente
22
Otro problema es que esto es diferente de <compilation targetFramework "4.5" />
Andrew
3

¿Mi prueba es defectuosa o hay algún elemento web.config que me falta aquí que haría que HttpContext.Current se resolviera correctamente después de una espera?

Su prueba no tiene fallas y HttpContext.Current no debe ser nulo después de la espera porque en ASP.NET Web API cuando espera, esto asegurará que el código que sigue a esta espera se pase el HttpContext correcto que estaba presente antes de la espera.

Darin Dimitrov
fuente
¿Está seguro del mismo hilo de continuación para WebAPI? Me ocupé del caso en el que era un hilo diferente.
noseratio
4
ASP.NET se reanudará en cualquier subproceso del grupo de subprocesos, pero con el contexto de solicitud correcto.
Stephen Cleary
2
Sí, tienes razón, es posible que el hilo no sea el mismo, pero HttpContext.Current será el mismo que antes de la espera. He actualizado mi pregunta.
Darin Dimitrov
4
HttpContext.Current es nulo después de una espera en mi código, y estoy apuntando a .net 4.6.1.
Triynko
1
para mí HttpContext.Current es nulo antes de una función de espera
JobaDiniz
2

Me encontré con este problema recientemente. Como señaló Stephen, no establecer explícitamente el marco objetivo puede generar este problema.

En mi caso, nuestra API web se migró a la versión 4.6.2, pero el marco de destino de tiempo de ejecución nunca se especificó en la configuración web, por lo que básicamente faltaba dentro de la etiqueta <system.web>:

Si tiene dudas sobre la versión del marco que está ejecutando, esto puede ayudar: agregue la siguiente línea en cualquiera de sus métodos de API web y establezca un punto de interrupción para verificar qué tipo está cargado actualmente en tiempo de ejecución y verificar que no sea una implementación heredada:

Debería ver esto (AspNetSynchronizationContext):

ingrese la descripción de la imagen aquí

En lugar de LegazyAspNetSynchronizationContext (que fue lo que vi antes de agregar el marco de destino):

ingrese la descripción de la imagen aquí

Si va al código fuente ( https://referencesource.microsoft.com/#system.web/LegacyAspNetSynchronizationContext.cs ) verá que la implementación heredada de esta interfaz carece de soporte asincrónico.

ingrese la descripción de la imagen aquí

Pasé mucho tiempo tratando de encontrar la fuente del problema y la respuesta de Stephen ayudó mucho. Espero que esta respuesta proporcione más información sobre el problema.

abarrenechea
fuente