without run method español ejemplos await async .net wcf .net-4.5 async-await

.net - run - OperationContext.Current es nulo después de aguardar primero cuando se usa async/await en el servicio WCF



wcf async (7)

Estoy usando el patrón async / await en .NET 4.5 para implementar algunos métodos de servicio en WCF. Ejemplo de servicio:

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 await primero OperationContext.Current devuelve null y no puedo acceder a OperationContext.Current.IncomingMessageHeaders .

En este sencillo ejemplo, esto no es un problema ya que puedo capturar el contexto antes de la await . Pero en el caso real se está accediendo a 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 manera de obtener el contexto de operación después del punto de await sin pasarlo por la pila manualmente?


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 resolver el problema registrando HttpContext en mi contenedor DI (Application_BeginRequest) y lo resuelvo cuando lo necesito.

Registro:

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

Resolver:

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


Afortunadamente para nosotros, nuestra implementación del servicio real se instancia a través del contenedor Unity IoC. Eso nos permitió crear un IWcfOperationContext que se configuró 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 OperationContext.Current y luego todos los lugares que lo requieren lo obtienen de IWcfOperationContext . Esto es lo que Stephen Cleary sugirió en su respuesta.


Ampliando la opción # 1 de Mr. Cleary, se puede colocar el siguiente código 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 tenga problemas con un contexto nulo, 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";

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


Aquí hay una implementación de ejemplo 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 el 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); }


Creo que tu 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ñádelo a LogicalCallContext .
  2. Instale su propio SynchronizationContext que establecerá OperationContext.Current cuando haga una Post ; así es como ASP.NET preserva su HttpContext.Current .
  3. Instale su propio TaskScheduler que establece OperationContext.Current .

También es posible que desee plantear este problema en Microsoft Connect.


Es desafortunado que esto no funcione y veamos cómo obtener una solución en una versión futura.

Mientras tanto, hay una manera de volver a aplicar el contexto al hilo actual para que no tenga que pasar el objeto:

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 método DoSomethingElse () tendrá acceso a OperationContext.Current como se esperaba.


Parece estar corregido en .Net 4.6.2. Ver el anuncio