c# .net async-await .net-4.6

c# - ¿En qué se diferencia la semántica de AsyncLocal del contexto de la llamada lógica?



.net async-await (3)

.NET 4.6 presenta la AsyncLocal<T> para el flujo de datos ambientales a lo largo del flujo de control asíncrono. Anteriormente utilicé CallContext.LogicalGet/SetData para este propósito, y me pregunto si los dos son semánticamente diferentes y de qué manera (más allá de las diferencias API obvias como la escritura fuerte y la falta de confianza en las teclas de cadena).


Me pregunto si los dos son semánticamente diferentes y de qué manera

Por lo que se puede ver, tanto CallContext como AsyncLocal confían internamente en ExecutionContext para almacenar sus datos internos dentro de un Dictionary . Este último parece estar agregando otro nivel de indirección para llamadas asíncronas. CallContext ha existido desde .NET Remoting y era una forma conveniente de transmitir datos entre llamadas asíncronas donde no había una alternativa real, hasta ahora.

La mayor diferencia que puedo detectar es que AsyncLocal ahora le permite registrarse en las notificaciones a través de una devolución de llamada cuando se cambia un valor almacenado subyacente, ya sea por un interruptor ExecutionContext o reemplazando explícitamente un valor existente.

// AsyncLocal<T> also provides optional notifications // when the value associated with the current thread // changes, either because it was explicitly changed // by setting the Value property, or implicitly changed // when the thread encountered an "await" or other context transition. // For example, we might want our // current culture to be communicated to the OS as well: static AsyncLocal<Culture> s_currentCulture = new AsyncLocal<Culture>( args => { NativeMethods.SetThreadCulture(args.CurrentValue.LCID); });

Aparte de eso, uno reside en System.Threading mientras que el otro vive en System.Runtime.Remoting , donde el primero será compatible con CoreCLR.

Además, no parece que AsyncLocal tenga la semántica superficial de copia en escritura que SetLogicalData tiene, por lo que los datos fluyen entre llamadas sin ser copiados.


La semántica es más o menos la misma. Ambos se almacenan en el ExecutionContext y fluyen a través de llamadas asíncronas.

Las diferencias son los cambios de API (tal como lo describió) junto con la capacidad de registrar una devolución de llamada para cambios de valor.

Técnicamente, hay una gran diferencia en la implementación ya que el CallContext se clona cada vez que se copia (usando CallContext.Clone ) mientras que los datos de AsyncLocal se guardan en el diccionario ExecutionContext._localValues y solo esa referencia se copia sin ningún trabajo adicional. .

Para asegurarse de que las actualizaciones solo afectan el flujo actual cuando cambia el valor de AsyncLocal , se crea un nuevo diccionario y todos los valores existentes se copian al nuevo.

Esa diferencia puede ser buena y mala para el rendimiento, dependiendo de dónde se use AsyncLocal .

Ahora, como Hans Passant mencionó en los comentarios, CallContext se hizo originalmente para la comunicación remota, y no está disponible donde no se admite la comunicación remota (por ejemplo, .Net Core), por lo que probablemente se agregó AsyncLocal al marco:

#if FEATURE_REMOTING public LogicalCallContext.Reader LogicalCallContext { [SecurityCritical] get { return new LogicalCallContext.Reader(IsNull ? null : m_ec.LogicalCallContext); } } public IllogicalCallContext.Reader IllogicalCallContext { [SecurityCritical] get { return new IllogicalCallContext.Reader(IsNull ? null : m_ec.IllogicalCallContext); } } #endif

Nota: también hay un AsyncLocal en el SDK de Visual Studio que es básicamente un contenedor sobre CallContext que muestra cuán similares son los conceptos: Microsoft.VisualStudio.Threading .


Parece haber alguna diferencia semántica en el tiempo.

Con CallContext, el cambio de contexto ocurre cuando se configura el contexto para el hilo secundario / tarea / método asincrónico, es decir, cuando se llama a Task.Factory.StartNew (), Task.Run () o al método asincrónico.

Con AsyncLocal, el cambio de contexto (llamada de devolución de llamada de notificación de cambio) ocurre cuando el hilo secundario / tarea / método asincrónico comienza a ejecutarse.

La diferencia de tiempo podría ser interesante, especialmente si desea que el objeto de contexto se clone cuando se cambia el contexto. El uso de diferentes mecanismos podría dar lugar a la clonación de diferentes contenidos: con CallContext clona el contenido cuando se crea el subproceso / tarea hijo o se llama al método asíncrono; pero con AsyncLocal clonas el contenido cuando el subproceso secundario / tarea / método asincrónico comienza a ejecutarse, el subproceso principal podría haber cambiado el contenido del objeto de contexto.