Conceptos fundamentales de AsyncLocal
AsyncLocal es un tipo diseñado para almacenar y transmitir estado en entornos asíncronos. Permite compartir datos entre hilos o tareas sin verse afectado por los cambios de contexto asíncrono.
Cada flujo asíncrono posee sus propios datos independientes
- AsyncLocal se utiliza principalmente para compartir objetos dentro del mismo flujo de control asíncrono, por ejemplo: una solicitud web que atraviesa múltiples llamadas a métodos async/await (posiblemente cambiando de hilo varias veces) puede seguir compartiendo el mismo objeto.
- AsyncLocal presenta una característica de anidamiento jerárquico, a diferencia de ThreadLocal que permanece fijo en un solo hilo. AsyncLocal opera sobre flujos de control asíncronos de tipo arbóreo.
class Aplicacion
{
private static AsyncLocal<contextosolicitud> contextoAsincrono = new AsyncLocal<contextosolicitud>();
static void Main(string[] args)
{
//Simular 5 solicitudes HTTP concurrentes
for (var i = 0; i < 5; i++)
{
var indice = i;
Task.Factory.StartNew(async () =>
{
var ctx = contextoAsincrono.Value = new ContextoSolicitud();
ctx.NombreSolicitud = "Solicitud" + indice;
ctx.Identificador = indice;
Console.WriteLine($"Antes del retardo IdHilo:{Thread.CurrentThread.ManagedThreadId} ctx.Nombre={ctx.NombreSolicitud} ctx.Id={ctx.Identificador}");
await Task.Delay(new Random().Next(1000, 2000));
Console.WriteLine($"Después del retardo IdHilo:{Thread.CurrentThread.ManagedThreadId} ctx.Nombre={ctx.NombreSolicitud} ctx.Id={ctx.Identificador}");
});
}
Console.Read();
}
}
class ContextoSolicitud
{
public string NombreSolicitud { get; set; }
public int Identificador { get; set; }
}
</contextosolicitud></contextosolicitud>
Comportamiento de AsyncLocal en flujos de control asíncronos arbóreos
- Cada nodo puede tener su propio objeto.
- Cuendo un nodo hijo no establece un objeto, accede al objeto del nodo padre.
- Cuando un nodo hijo establece un objeto, accede al objeto que él mismo configuró.
- El nodo padre no puede acceder a los objetos establecidos por los nodos hijos.
class Aplicacion
{
private static AsyncLocal<contextosolicitud> contextoFlujo = new AsyncLocal<contextosolicitud>();
static async Task Main(string[] args)
{
await MetodoPadre();
Console.Read();
}
//Contexto del nivel superior
public static async Task MetodoPadre()
{
contextoFlujo.Value = new ContextoSolicitud
{
Identificador = 0,
NombreSolicitud = "Padre"
};
Console.WriteLine("Padre:" + contextoFlujo.Value);
await MetodoHijo();
Console.WriteLine("Padre:" + contextoFlujo.Value);
}
//Contexto secundario
public static async Task MetodoHijo()
{
Console.WriteLine("Hijo - Antes:" + contextoFlujo.Value);
contextoFlujo.Value = new ContextoSolicitud
{
NombreSolicitud = "Hijo",
Identificador = 1,
};
Console.WriteLine("Hijo - Después de modificar");
Console.WriteLine("Hijo - Actual:" + contextoFlujo.Value);
}
}
class ContextoSolicitud
{
public string NombreSolicitud { get; set; }
public int Identificador { get; set; }
public override string ToString()
{
return $"Nombre={NombreSolicitud},Id={Identificador}";
}
}
</contextosolicitud></contextosolicitud>
Escenarios de uso de AsyncLocal
Transmisión de datos de estado: En operaciones asíncronas, como métodos asíncronos o cadenas de tareas, necesitamos compartir ciertos datos de estado. Utilizando AsyncLocal, podemos transmitir estos datos entre operaciones asíncronas sin necesidad de pasarlos explícitamente como parámetros.
Información contextual relcaionada: En ocasiones, necesitamos acceder a información contextual a través de métodos o tareas asíncronas, como información de autenticación de usuarios o configuración de idioma. Con AsyncLocal, podemos acceder a esta información en toda la pila de llamadas asíncronas sin necesidad de pasarla como parámetro en cada método.
//En la misma solicitud web, obtener datos del contexto del comerciante
//serán idénticos y no afectarán a otras solicitudes web
public class ContextoActual
{
private static readonly AsyncLocal<usuarioactual> UsuarioActual = new AsyncLocal<usuarioactual>();
public static void EstablecerDatosActuales(UsuarioActual usuario)
{
UsuarioActual.Value = usuario;
}
public static UsuarioActual ObtenerDatosActuales()
{
return UsuarioActual.Value ?? new UsuarioActual();
}
}
</usuarioactual></usuarioactual>