AsyncLocal的基本概念
AsyncLocal是一個在異步環境中存儲和傳遞狀態的類型。它允許你在線程或任務之間共享數據,而不會受到異步上下文切換的影響。
每一個異步的AsyncLocal的數據都是獨立的
- AsyncLocal主要是用來在同一個異步控制流內共享對象的,如:一個web請求經過多個 async/await 方法調用後(可能切換了多個線程)依然可以共享同一個對象;
- AsyncLocal存在層級嵌套的特點,不像ThreadLocal一個線程到底,也就是說AsyncLocal是工作在樹形的異步控制流上的;
class Program
{
private static AsyncLocal<WebContext> threadLocal = new AsyncLocal<WebContext>();
static void Main(string[] args)
{
//模擬5個HTTP請求
for (var i = 0; i < 5; i++)
{
var index = i;
Task.Factory.StartNew(async () =>
{
var ctx = threadLocal.Value = new WebContext();
ctx.Name = "請求" + index;
ctx.Id = index;
Console.WriteLine($"Delay前 線程ID:{Thread.CurrentThread.ManagedThreadId} ctx.Name={ctx.Name} ctx.Id={ctx.Id}");
await Task.Delay(new Random().Next(1000, 2000));
Console.WriteLine($"Delay後 線程ID:{Thread.CurrentThread.ManagedThreadId} ctx.Name={ctx.Name} ctx.Id={ctx.Id}");
});
}
Console.Read();
}
}
class WebContext
{
public string Name { get; set; }
public int Id { get; set; }
}
AsyncLocal在樹形異步控制流上流動的特點:
- 每個節點都可以有自己的對象;
- 當子節點沒有設置對象時,則訪問的是父節點的對象;
- 當子節點設置了對象時,則訪問自己設置的對象;
- 父節點無法訪問子節點設置的對象;
class Program
{
private static AsyncLocal<WebContext> asyncLocal = new AsyncLocal<WebContext>();
static async Task Main(string[] args)
{
await Async();
Console.Read();
}
//父上下文
public static async Task Async()
{
asyncLocal.Value = new WebContext
{
Id = 0,
Name = "父"
};
Console.WriteLine("父:" + asyncLocal.Value);
await Async1();
Console.WriteLine("父:" + asyncLocal.Value);
}
//子上下文
public static async Task Async1()
{
Console.WriteLine("子子:" + asyncLocal.Value);
asyncLocal.Value = new WebContext
{
Name = "子",
Id = 1,
};
Console.WriteLine("子子:修改後");
Console.WriteLine("子子:" + asyncLocal.Value);
}
}
class WebContext
{
public string Name { get; set; }
public int Id { get; set; }
public override string ToString()
{
return $"Name={Name},Id={Id}";
}
}
AsyncLocal的使用場景
- 傳遞狀態數據:在異步操作中,例如異步方法或任務鏈中,我們可能需要共享某些狀態數據。使用AsyncLocal,我們可以在異步操作之間傳遞這些狀態數據,而不必顯式地傳遞參數。
- 上下文相關信息:有時候,我們可能需要跨異步方法或任務訪問一些上下文相關的信息,例如用戶身份驗證信息、語言設置等。使用AsyncLocal,我們可以在整個異步調用棧中訪問這些信息,而不必在每個方法中傳遞它們作爲參數。
//同一個web請求獲取 商戶上下文數據都是一樣的,而且不會影響另外一個web請求
public class CurrentContext
{
/// <summary>
/// 商戶
/// </summary>
private static readonly AsyncLocal<CurrentUser> CurrentUser = new AsyncLocal<CurrentUser>();
public static void SetCurrentData(CurrentUser currentUser)
{
CurrentUser.Value = currentUser;
}
public static CurrentUser GetCurrentData()
{
return CurrentUser.Value??new CurrentUser();
}
}