AsyncLocal是一个在.NET中用来在同步任务和异步任务中保持全局变量的工具类。它允许你在不同线程的同一个对象中保留一个特定值,这样你可以在不同的函数和任务中访问这个值。这是在实现异步任务中维持一致性和优雅性的一种重要手段。
中间件中使用
csharp
public class BaseClass
{
public string Id { get; set; }
}
public static class AmbientContext
{
public static readonly ConcurrentDictionary<string, AsyncLocal<object?>>
_contexts = new(StringComparer.Ordinal);
public static void Set<T>(string key, [MaybeNull] T val)
{
AsyncLocal<object?> keyctx = _contexts.AddOrUpdate(
key,
k => new AsyncLocal<object?>(),
(k, al) => al);
keyctx.Value = (object?)val;
}
[return: MaybeNull]
public static T Get<T>(string key)
{
return _contexts.TryGetValue(key, out AsyncLocal<object?>? keyctx)
? (T)(keyctx!.Value ?? default(T)!)
: default(T);
}
}
public class AmbientContextMiddleware
{
private readonly RequestDelegate _next;
public AmbientContextMiddleware(RequestDelegate next) =>
_next = next;
public async Task Invoke(HttpContext context)
{
string corrId =
context.Request
.Headers["x-foocorp-correlationId"]
.FirstOrDefault(Guid.NewGuid().ToString());
context.Request.Headers.Add("x-foocorp-correlationId", corrId);
AmbientContext.Set<BaseClass>(corrId, new BaseClass() { Id=DateTime.Now.ToString()});
await _next.Invoke(context);
// TODO: emit corrid response header, esp if _we_ created it
}
}
[HttpGet]
public async Task<DatasetSmallMovie> GetByID(string id,string indexName)
{
Console.WriteLine(DateTime.Now.ToString());
Request.Headers.TryGetValue("x-foocorp-correlationId", out var headersvalue);
Console.WriteLine(headersvalue);
await Task.Delay(2000);
Console.WriteLine(AmbientContext.Get<BaseClass>(headersvalue).Id);
return await _meilisearchHelper.GetByID<DatasetSmallMovie>(id, indexName);
}
普通使用
csharp
using System;
using System.Threading;
using System.Threading.Tasks;
public class LogContext { public string StackTrace { get; set; } public string UserInfo { get; set; } }
public class TenantContext { public string Name { get; set; } }
public class Program
{
private static AsyncLocal<LogContext> _logContext = new AsyncLocal<LogContext>(); private static AsyncLocal<TenantContext> _tenantContext = new AsyncLocal<TenantContext>();
public static async Task Main(string[] args)
{
_logContext.Value = new LogContext { StackTrace = "Main Stack Trace", UserInfo = "User1" }; _tenantContext.Value = new TenantContext { Name = "Tenant A" };
Console.WriteLine($"Initial Log Context: {_logContext.Value.StackTrace}, User: {_logContext.Value.UserInfo}, Tenant: {_tenantContext.Value.Name}");
await Task.Run(() => LogAndProcess(new LogContext { StackTrace = "Child Stack Trace", UserInfo = "User2" }, new TenantContext { Name = "Tenant B" }));
Console.WriteLine($"After Task Log Context: {_logContext.Value.StackTrace}, User: {_logContext.Value.UserInfo}, Tenant: {_tenantContext.Value.Name}");
}
private static void LogAndProcess(LogContext logContext, TenantContext tenant)
{
_logContext.Value = logContext; _tenantContext.Value = tenant;
Console.WriteLine($"In Task Log Context: {_logContext.Value.StackTrace}, User: {_logContext.Value.UserInfo}, Tenant: {_tenantContext.Value.Name}");
// Simulate some processing Task.Delay(1000).Wait();
Console.WriteLine($"After Processing Log Context: {_logContext.Value.StackTrace}, User: {_logContext.Value.UserInfo}, Tenant: {_tenantContext.Value.Name}");
}
}