2012-10-09 20 views
34

Estoy usando el patrón async/await en .NET 4.5 para implementar algunos métodos de servicio en WCF. Servicio Ejemplo:OperationContext.Current es nulo después de aguardar cuando se utiliza async/await en el servicio WCF

Contrato:

[ServiceContract(Namespace = "http://async.test/")] 
public interface IAsyncTest 
{ 
    Task DoSomethingAsync(); 
} 

Implementación:

MyAsyncService : IAsyncTest 
{ 
    public async Task DoSomethingAsync() 
    { 
     var context = OperationContext.Current; // context is present 

     await Task.Delay(10); 

     context = OperationContext.Current; // context is null 
    } 
} 

El problema que estoy teniendo es que después de las primeras declaraciones de awaitOperationContext.Currentnull y no puedo acceder OperationContext.Current.IncomingMessageHeaders.

En este sencillo ejemplo esto no es un problema ya que puedo capturar el contexto antes del await. Pero en el mundo real se está accediendo al caso OperationContext.Current desde el interior de la pila de llamadas y realmente no quiero cambiar muchos códigos solo para pasar el contexto más allá.

¿Hay alguna forma de obtener el contexto de operación después del punto await sin pasarlo por la pila manualmente?

+0

¿Qué significa serializar una instancia 'Task' sobre el cable al cliente? – Steven

+0

Al usar async/await la tarea no se pasa al cliente. Wcf entiende eso como un método de devolución de nulo. El cliente que agrega una referencia a dicho servicio vería DoSomething() vacío; – mdonatas

+1

Eso es interesante. Aún así, no estoy seguro de que realmente quieras ejecutar las operaciones de esta manera. ¿Qué haces cuando la operación falla por alguna razón? El cliente piensa que tuvo éxito exitosamente. Será mejor que ponga en cola estas operaciones en una cola transaccional de algún tipo. – Steven

Respuesta

21

Creo que su mejor opción es capturarlo y pasarlo manualmente. Puede encontrar que esto mejora la capacidad de prueba de su código.

Dicho esto, hay un par de otras opciones:

  1. Añadir al LogicalCallContext.
  2. Instale su propio SynchronizationContext que establecerá OperationContext.Current cuando haga un Post; así es como ASP.NET preserva su HttpContext.Current.
  3. Instale su propio TaskScheduler que establece OperationContext.Current.

Es posible que también desee plantear este problema en Microsoft Connect.

+1

+1 para la captura + pase para la capacidad de prueba :) –

27

Desafortunadamente, esto no funciona y veremos cómo solucionarlo en una versión futura.

Por el momento, no existe una manera de volver a aplicar el contexto de la rosca actual, de modo que usted no tiene que pasar el objeto en torno a:

public async Task<double> Add(double n1, double n2) 
    { 

     OperationContext ctx = OperationContext.Current; 

     await Task.Delay(100); 

     using (new OperationContextScope(ctx)) 
     { 
      DoSomethingElse(); 
     } 
     return n1 + n2; 
    } 

En el ejemplo anterior, el DoSomethingElse () El método tendrá acceso a OperationContext.Current como se esperaba.

+5

Jon: usted sabe claramente más que la mayoría de la gente con un representante de 16 (y encontré su blog en MSDN). Puedo sugerirle que actualice su perfil para que la gente entienda la calidad de su respuesta. – ErnieL

+0

Jon, solo estamos tratando de decidir un acercamiento al tema de esta pregunta, me preguntaba si podría explicar por qué, en su código anterior, usa OperationContextScope, en lugar de solo usar el ctx directamente? (He publicado más detalle aquí http://stackoverflow.com/questions/13290146/async-wcf-method-weboperationcontext-is-null-after-await) –

+2

La razón principal para el uso OperationContextScope es conseguir OperationContext.Current establece en el que pasó al constructor. Esto evita que tengas que pasar la instancia ctx a una pila de llamadas profunda (por ejemplo, no tienes que modificar las firmas de tu método para tomar un parámetro OperationContext). – JonCole

2

Afortunadamente para nosotros, nuestra implementación del servicio real se instancia a través del contenedor IoC Unity. Eso nos permitió crear un IWcfOperationContext que fue configurado para tener un PerResolveLifetimeManager que simplemente significa que habrá solo una instancia de WcfOperationContext para cada instancia de nuestro RealService.
En el constructor de WcfOperationContext capturamos OperationContext.Current y luego todos los lugares que lo requieren lo obtienen de IWcfOperationContext. Esto es lo que Stephen Cleary sugirió en su respuesta.

3

Aquí está un ejemplo de implementación SynchronizationContext:

public class OperationContextSynchronizationContext : SynchronizationContext 
{ 
    private readonly OperationContext context; 

    public OperationContextSynchronizationContext(IClientChannel channel) : this(new OperationContext(channel)) { } 

    public OperationContextSynchronizationContext(OperationContext context) 
    { 
     OperationContext.Current = context; 
     this.context = context; 
    } 

    public override void Post(SendOrPostCallback d, object state) 
    { 
     OperationContext.Current = context; 
     d(state); 
    } 
} 

y uso:

var currentSynchronizationContext = SynchronizationContext.Current; 
try 
{ 
    SynchronizationContext.SetSynchronizationContext(new OperationContextSynchronizationContext(client.InnerChannel)); 
    var response = await client.RequestAsync(); 
    // safe to use OperationContext.Current here 
} 
finally 
{ 
    SynchronizationContext.SetSynchronizationContext(currentSynchronizationContext); 
} 
+0

En el caso de niveles anidados de llamadas esperadas, el actual SynchronizationContext no se reemplaza. Así que tuve que extender el método Post anulado con una comprobación, si un SynchronizationContext.Current es nulo, llamo a SynchronizationContext.SetSynchronizationContext (this). No sé si es la manera adecuada, sin embargo, funcionó para mí. –

3

Ampliando la opción # 1 del Sr. Cleary, el siguiente código se puede colocar en el constructor del servicio WCF para almacenar y recuperar el OperationContext en el contexto de llamada lógica:

if (CallContext.LogicalGetData("WcfOperationContext") == null) 
{ 
    CallContext.LogicalSetData("WcfOperationContext", OperationContext.Current); 
} 
else if (OperationContext.Current == null) 
{ 
    OperationContext.Current = (OperationContext)CallContext.LogicalGetData("WcfOperationContext"); 
} 

Con eso, en cualquier lugar que está teniendo problemas con un contexto nulo se puede escribir algo como lo siguiente:

var cachedOperationContext = CallContext.LogicalGetData("WcfOperationContext") as OperationContext; 
var user = cachedOperationContext != null ? cachedOperationContext.ServiceSecurityContext.WindowsIdentity.Name : "No User Info Available"; 

responsabilidad: Este es el código de años de edad y no recuerdo la razón por la que necesitaba el else if en el constructor, pero tenía algo que ver con la asincronización y sé que era necesario en mi caso.

-2

Actualización: Como se señala en los comentarios a continuación, esta solución no es segura para subprocesos, por lo que supongo que las soluciones discutidas anteriormente siguen siendo la mejor manera.

Me muevo por el problema registrando el HttpContext en mi contenedor DI (Application_BeginRequest) y lo resuelvo cuando lo necesito.

Registro:

this.UnityContainer.RegisterInstance<HttpContextBase>(new HttpContextWrapper(HttpContext.Current)); 

Resolve:

var context = Dependencies.ResolveInstance<HttpContextBase>(); 
+0

Tengo dudas de que esto sea seguro para subprocesos. Me gusta lo que sucede si hay dos solicitudes (A y B) básicamente al mismo tiempo. A registra esto, sigue adelante, B registra esto y A resuelve la instancia ... instancia puesta allí por B. – mdonatas

+0

Gracias por sus comentarios, @mdonates. De hecho, podría plantear problemas como los que usted describió. Regresaré a mi tablero de dibujo y resolveré algo más. Aclamaciones. – wind23

6

Parece ser fijado en .Net 4.6.2. Ver the announcement

+0

Aparece en 4.6.2 y mi OperationContext sigue siendo nulo en espera de llamadas. He encontrado un no agregar esto también appSettings, pero nada ha cambiado: Rhyous

+0

@Rhyous estás esperando con 'configureAwait (false)' por casualidad ? – DixonD

+0

Me preocupa que mi proyecto sea único. Mi proyecto se llama Entity Anywhere Framework. Es un marco de aplicaciones comerciales, diseñado con la idea de que tendré una docena de sistemas y entidades en todos ellos, y quiero construir sobre ellos. De todos modos, estoy generando dinámicamente el servicio REST wcf por entidad. Necesito probar un proyecto predeterminado usando mi diseño. https://github.com/rhyous/EntityAnywhere – Rhyous

Cuestiones relacionadas