2012-06-29 4 views
5

Estoy tratando de mezclar AsyncController con la inyección de dependencia. La aplicación MVC en cuestión obtiene casi todos sus datos a través de llamadas de servicio web asincrónicas. Estamos ajustando el trabajo asincrónico en Tareas del TPL y notificamos al AsyncManager del controlador cuando se completan estas tareas.Ejecutando una tarea a través del contexto de sincronización de ASP.NET de manera segura y sin AsyncManager.Sync

A veces tenemos que tocar el HttpContext en la continuación de estas tareas: agregar una cookie, lo que sea. La forma correcta de hacerlo de acuerdo con Using an Asynchronous Controller in ASP.NET MVC es llamar al método AsyncManager.Sync. Esto propagará el contexto del subproceso ASP.NET, incluido el HttpContext, al subproceso actual, ejecutará la devolución de llamada y luego restaurará el contexto anterior.

Sin embargo, dicho artículo también dice:

de llamar a sync() de un hilo que ya está bajo el control de ASP.NET ha indefinido comportamiento.

Esto no es un problema si haces todo tu trabajo en el controlador, ya que generalmente sabes en qué hilo deberías estar en las continuaciones. Pero lo que intento hacer es crear una capa intermedia entre nuestro acceso de datos asíncronos y nuestros controladores asíncronos. Entonces estos también son asincrónicos. Todo está conectado por un contenedor DI.

Como antes, algunos de los componentes en una cadena de llamadas necesitarán trabajar con el HttpContext "actual". Por ejemplo, después del inicio de sesión, queremos almacenar el token de "sesión" que obtenemos de un servicio de inicio de sesión único. La abstracción de lo que hace eso es ISessionStore. Piense en una CookieSessionStore que coloca una cookie en la respuesta o toma la cookie de la solicitud.

dos problemas que puedo ver con esto:

  1. Los componentes no tienen acceso a AsyncManager o incluso saben que están siendo utilizados dentro de un controlador.
  2. Los componentes no saben de qué hilo se están invocando, por lo que AsyncManager.Sync o cualquier equivalente es teóricamente problemático de todos modos.

Para resolver # 1, básicamente me estoy inyectando un objeto que agarra TaskScheduler.FromCurrentSynchronizationContext() al inicio de la solicitud, y se puede invocar una acción a través de una tarea iniciada con el planificador, tomando el HttpContextBase como argumento.

Eso es, de mis componentes, puedo llamar a algo similar a:

MySyncObject.Sync(httpContext => /* Add a cookie or something else */); 

todavía no he observado ningún problema con esto, pero estoy preocupado por el problema 2 #. He visto tanto AsyncManager como SynchronizationContextTaskScheduler en Reflector, y funcionan de manera similar, ejecutando la devolución de llamada en ASP.NET SynchronizationContext. Y eso me asusta :)

Tenía un poco de esperanza cuando vi que la implementación del programador de tareas invocará directamente en lugar de pasar por el contexto de sincronización si está en línea. Pero desafortunadamente, esto no parece suceder a través de la ruta de código Task.Start(scheduler) normal. Por el contrario, las tareas pueden incluirse en otras circunstancias, como si se esperaran antes de que comiencen.

Así que mis preguntas son:

  1. voy a tener problemas aquí con este enfoque?
  2. ¿Hay una manera mejor?
  3. ¿La utilidad de la sincronización en este escenario es meramente serializar el acceso al HttpContext no seguro para subprocesos? es decir, ¿podría salirme con un contenedor HttpContextBase seguro para subprocesos (ick)?
+0

Si usa 'Task.Start (scheduler)', aún es posible que se inserte, si 'Wait()' lo hace lo suficientemente rápido. Por supuesto, estamos hablando de multihilo, por lo que es posible que "inmediatamente" no sea "lo suficientemente rápido". – svick

+0

Hmm, no veo eso. Por supuesto, tengo .NET 4.5 instalado ahora, ¿quién sabe? :) –

Respuesta

1

Basarse en la estática local de subprocesos rara vez es una buena idea. Mientras que HttpContext.Current se basa en ese mecanismo y ha funcionado durante años, ahora que nos estamos acercando, ese enfoque se está deteriorando rápidamente. Es mucho mejor capturar el valor de esta variable estática como variable local y pasar eso con su trabajo asíncrono para que siempre lo tenga. Así, por ejemplo:

public async Task<ActionResult> MyAction() { 
    var context = HttpContext.Current; 
    await Task.Yield(); 
    var item = context.Items["something"]; 
    await Task.Yield(); 
    return new EmptyResult(); 
} 

O mejor aún, evitar HttpContext.Current por completo si estás en MVC:

public async Task<ActionResult> MyAction() { 
    await Task.Yield(); 
    var item = this.HttpContext.Items["something"]; 
    await Task.Yield(); 
    return new EmptyResult(); 
} 

Podría decirse que la lógica de negocio de middleware en especial no debe ser un partido basado en HttpContext o cualquier otra cosa en las bibliotecas ASP.NET. Por lo tanto, suponiendo que sus llamadas de middleware a su controlador (a través de devoluciones de llamada, interfaces, etc.) para establecer cookies, tendrá this.HttpContext disponible para usar para acceder a ese contexto.

Cuestiones relacionadas