.NET?Core利用?AsyncLocal?實(shí)現(xiàn)共享變量的代碼詳解
簡(jiǎn)介
我們?nèi)绻枰麄€(gè)程序共享一個(gè)變量,我們僅需將該變量放在某個(gè)靜態(tài)類(lèi)的靜態(tài)變量上即可(不滿(mǎn)足我們的需求,靜態(tài)變量上,整個(gè)程序都是固定值)。我們?cè)赪eb 應(yīng)用程序中,每個(gè)Web 請(qǐng)求服務(wù)器都為其分配了一個(gè)獨(dú)立線(xiàn)程,如何實(shí)現(xiàn)用戶(hù),租戶(hù)等信息隔離在這些獨(dú)立線(xiàn)程中。這就是今天要說(shuō)的線(xiàn)程本地存儲(chǔ)。針對(duì)線(xiàn)程本地存儲(chǔ) .NET 給我們提供了兩個(gè)類(lèi) ThreadLocal 和 AsyncLocal。我們可以通過(guò)查看以下例子清晰的看到兩者的區(qū)別:
[TestClass]
public class TastLocal {
private static ThreadLocal<string> threadLocal = new ThreadLocal<string>();
private static AsyncLocal<string> asyncLocal = new AsyncLocal<string>();
[TestMethod]
public void Test() {
threadLocal.Value = "threadLocal";
asyncLocal.Value = "asyncLocal";
var threadId = Thread.CurrentThread.ManagedThreadId;
Task.Factory.StartNew(() => {
var threadId = Thread.CurrentThread.ManagedThreadId;
Debug.WriteLine($"StartNew:threadId:{ threadId}; threadLocal:{threadLocal.Value}");
Debug.WriteLine($"StartNew:threadId:{ threadId}; asyncLocal:{asyncLocal.Value}");
});
CurrThread();
}
public void CurrThread() {
var threadId = Thread.CurrentThread.ManagedThreadId;
Debug.WriteLine($"CurrThread:threadId:{threadId};threadLocal:{threadLocal.Value}");
Debug.WriteLine($"CurrThread:threadId:{threadId};asyncLocal:{asyncLocal.Value}");
}
}
輸出結(jié)果:
CurrThread:threadId:4;threadLocal:threadLocal
StartNew:threadId:11; threadLocal:
CurrThread:threadId:4;asyncLocal:asyncLocal
StartNew:threadId:11; asyncLocal:asyncLocal
從上面結(jié)果中可以看出 ThreadLocal 和 AsyncLocal 都能實(shí)現(xiàn)基于線(xiàn)程的本地存儲(chǔ)。但是當(dāng)線(xiàn)程切換后,只有 AsyncLocal 還能夠保留原來(lái)的值。在Web 開(kāi)發(fā)中,我們會(huì)有很多異步場(chǎng)景,在這些場(chǎng)景下,可能會(huì)出現(xiàn)線(xiàn)程的切換。所以我們使用AsyncLocal 去實(shí)現(xiàn)在Web 應(yīng)用程序下的共享變量。
AsyncLocal 解讀
源碼查看:
public sealed class AsyncLocal<T> : IAsyncLocal
{
private readonly Action<AsyncLocalValueChangedArgs<T>>? m_valueChangedHandler;
//
// 無(wú)參構(gòu)造函數(shù)
//
public AsyncLocal()
{
}
//
// 構(gòu)造一個(gè)帶有委托的AsyncLocal<T>,該委托在當(dāng)前值更改時(shí)被調(diào)用
// 在任何線(xiàn)程上
//
public AsyncLocal(Action<AsyncLocalValueChangedArgs<T>>? valueChangedHandler)
{
m_valueChangedHandler = valueChangedHandler;
}
[MaybeNull]
public T Value
{
get
{
object? obj = ExecutionContext.GetLocalValue(this);
return (obj == null) ? default : (T)obj;
}
set => ExecutionContext.SetLocalValue(this, value, m_valueChangedHandler != null);
}
void IAsyncLocal.OnValueChanged(object? previousValueObj, object? currentValueObj, bool contextChanged)
{
Debug.Assert(m_valueChangedHandler != null);
T previousValue = previousValueObj == null ? default! : (T)previousValueObj;
T currentValue = currentValueObj == null ? default! : (T)currentValueObj;
m_valueChangedHandler(new AsyncLocalValueChangedArgs<T>(previousValue, currentValue, contextChanged));
}
}
//
// 接口,允許ExecutionContext中的非泛型代碼調(diào)用泛型AsyncLocal<T>類(lèi)型
//
internal interface IAsyncLocal
{
void OnValueChanged(object? previousValue, object? currentValue, bool contextChanged);
}
public readonly struct AsyncLocalValueChangedArgs<T>
{
public T? PreviousValue { get; }
public T? CurrentValue { get; }
//
// If the value changed because we changed to a different ExecutionContext, this is true. If it changed
// because someone set the Value property, this is false.
//
public bool ThreadContextChanged { get; }
internal AsyncLocalValueChangedArgs(T? previousValue, T? currentValue, bool contextChanged)
{
PreviousValue = previousValue!;
CurrentValue = currentValue!;
ThreadContextChanged = contextChanged;
}
}
//
// Interface used to store an IAsyncLocal => object mapping in ExecutionContext.
// Implementations are specialized based on the number of elements in the immutable
// map in order to minimize memory consumption and look-up times.
//
internal interface IAsyncLocalValueMap
{
bool TryGetValue(IAsyncLocal key, out object? value);
IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullValueAsNonexistent);
}我們知道在.NET 里面,每個(gè)線(xiàn)程都關(guān)聯(lián)著執(zhí)行上下文。我們可以通 Thread.CurrentThread.ExecutionContext 屬性進(jìn)行訪(fǎng)問(wèn) 或者通過(guò) ExecutionContext.Capture() 獲取。
從上面我們可以看出 AsyncLocal 的 Value 存取是通過(guò) ExecutionContext.GetLocalValue 和GetLocalValue.SetLocalValue 進(jìn)行操作的,我們可以繼續(xù)從 ExecutionContext 里面取出部分代碼查看(源碼地址),為了更深入地理解 AsyncLocal 我們可以查看一下源碼,看看內(nèi)部實(shí)現(xiàn)原理。
internal static readonly ExecutionContext Default = new ExecutionContext();
private static volatile ExecutionContext? s_defaultFlowSuppressed;
private readonly IAsyncLocalValueMap? m_localValues;
private readonly IAsyncLocal[]? m_localChangeNotifications;
private readonly bool m_isFlowSuppressed;
private readonly bool m_isDefault;
private ExecutionContext()
{
m_isDefault = true;
}
private ExecutionContext(
IAsyncLocalValueMap localValues,
IAsyncLocal[]? localChangeNotifications,
bool isFlowSuppressed)
{
m_localValues = localValues;
m_localChangeNotifications = localChangeNotifications;
m_isFlowSuppressed = isFlowSuppressed;
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
throw new PlatformNotSupportedException();
}
public static ExecutionContext? Capture()
{
ExecutionContext? executionContext = Thread.CurrentThread._executionContext;
if (executionContext == null)
{
executionContext = Default;
}
else if (executionContext.m_isFlowSuppressed)
{
executionContext = null;
}
return executionContext;
}
internal static object? GetLocalValue(IAsyncLocal local)
{
ExecutionContext? current = Thread.CurrentThread._executionContext;
if (current == null)
{
return null;
}
Debug.Assert(!current.IsDefault);
Debug.Assert(current.m_localValues != null, "Only the default context should have null, and we shouldn't be here on the default context");
current.m_localValues.TryGetValue(local, out object? value);
return value;
}
internal static void SetLocalValue(IAsyncLocal local, object? newValue, bool needChangeNotifications)
{
ExecutionContext? current = Thread.CurrentThread._executionContext;
object? previousValue = null;
bool hadPreviousValue = false;
if (current != null)
{
Debug.Assert(!current.IsDefault);
Debug.Assert(current.m_localValues != null, "Only the default context should have null, and we shouldn't be here on the default context");
hadPreviousValue = current.m_localValues.TryGetValue(local, out previousValue);
}
if (previousValue == newValue)
{
return;
}
// Regarding 'treatNullValueAsNonexistent: !needChangeNotifications' below:
// - When change notifications are not necessary for this IAsyncLocal, there is no observable difference between
// storing a null value and removing the IAsyncLocal from 'm_localValues'
// - When change notifications are necessary for this IAsyncLocal, the IAsyncLocal's absence in 'm_localValues'
// indicates that this is the first value change for the IAsyncLocal and it needs to be registered for change
// notifications. So in this case, a null value must be stored in 'm_localValues' to indicate that the IAsyncLocal
// is already registered for change notifications.
IAsyncLocal[]? newChangeNotifications = null;
IAsyncLocalValueMap newValues;
bool isFlowSuppressed = false;
if (current != null)
{
Debug.Assert(!current.IsDefault);
Debug.Assert(current.m_localValues != null, "Only the default context should have null, and we shouldn't be here on the default context");
isFlowSuppressed = current.m_isFlowSuppressed;
newValues = current.m_localValues.Set(local, newValue, treatNullValueAsNonexistent: !needChangeNotifications);
newChangeNotifications = current.m_localChangeNotifications;
}
else
{
// First AsyncLocal
newValues = AsyncLocalValueMap.Create(local, newValue, treatNullValueAsNonexistent: !needChangeNotifications);
}
//
// Either copy the change notification array, or create a new one, depending on whether we need to add a new item.
//
if (needChangeNotifications)
{
if (hadPreviousValue)
{
Debug.Assert(newChangeNotifications != null);
Debug.Assert(Array.IndexOf(newChangeNotifications, local) >= 0);
}
else if (newChangeNotifications == null)
{
newChangeNotifications = new IAsyncLocal[1] { local };
}
else
{
int newNotificationIndex = newChangeNotifications.Length;
Array.Resize(ref newChangeNotifications, newNotificationIndex + 1);
newChangeNotifications[newNotificationIndex] = local;
}
}
Thread.CurrentThread._executionContext =
(!isFlowSuppressed && AsyncLocalValueMap.IsEmpty(newValues)) ?
null : // No values, return to Default context
new ExecutionContext(newValues, newChangeNotifications, isFlowSuppressed);
if (needChangeNotifications)
{
local.OnValueChanged(previousValue, newValue, contextChanged: false);
}
}
從上面可以看出,ExecutionContext.GetLocalValue 和GetLocalValue.SetLocalValue 都是通過(guò)對(duì) m_localValues 字段進(jìn)行操作的。
m_localValues 的類(lèi)型是 IAsyncLocalValueMap ,IAsyncLocalValueMap 的實(shí)現(xiàn) 和 AsyncLocal.cs 在一起,感興趣的可以進(jìn)一步查看 IAsyncLocalValueMap 是如何創(chuàng)建,如何查找的。
可以看到,里面最重要的就是ExecutionContext 的流動(dòng),線(xiàn)程發(fā)生變化時(shí)ExecutionContext 會(huì)在前一個(gè)線(xiàn)程中被默認(rèn)捕獲,流向下一個(gè)線(xiàn)程,它所保存的數(shù)據(jù)也就隨之流動(dòng)。在所有會(huì)發(fā)生線(xiàn)程切換的地方,基礎(chǔ)類(lèi)庫(kù)(BCL) 都為我們封裝好了對(duì)執(zhí)行上下文的捕獲 (如開(kāi)始的例子,可以看到 AsyncLocal 的數(shù)據(jù)不會(huì)隨著線(xiàn)程的切換而丟失),這也是為什么 AsyncLocal 能實(shí)現(xiàn) 線(xiàn)程切換后,還能正常獲取數(shù)據(jù),不丟失。
總結(jié)
AsyncLocal 本身不保存數(shù)據(jù),數(shù)據(jù)保存在 ExecutionContext 實(shí)例。
ExecutionContext 的實(shí)例會(huì)隨著線(xiàn)程切換流向下一線(xiàn)程(也可以禁止流動(dòng)和恢復(fù)流動(dòng)),保證了線(xiàn)程切換時(shí),數(shù)據(jù)能正常訪(fǎng)問(wèn)。
1.在.NET Core 中的使用示例先創(chuàng)建一個(gè)上下文對(duì)象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace NetAsyncLocalExamples.Context
{
/// <summary>
/// 請(qǐng)求上下文 租戶(hù)ID
/// </summary>
public class RequestContext
{
/// <summary>
/// 獲取請(qǐng)求上下文
/// </summary>
public static RequestContext Current => _asyncLocal.Value;
private readonly static AsyncLocal<RequestContext> _asyncLocal = new AsyncLocal<RequestContext>();
/// <summary>
/// 將請(qǐng)求上下文設(shè)置到線(xiàn)程全局區(qū)域
/// </summary>
/// <param name="userContext"></param>
public static IDisposable SetContext(RequestContext userContext)
{
_asyncLocal.Value = userContext;
return new RequestContextDisposable();
}
/// <summary>
/// 清除上下文
/// </summary>
public static void ClearContext()
{
_asyncLocal.Value = null;
}
/// <summary>
/// 租戶(hù)ID
/// </summary>
public string TenantId { get; set; }
}
}
namespace NetAsyncLocalExamples.Context
{
/// <summary>
/// 用于釋放對(duì)象
/// </summary>
internal class RequestContextDisposable : IDisposable
{
internal RequestContextDisposable() { }
public void Dispose()
{
RequestContext.ClearContext();
}
}
}
2.創(chuàng)建請(qǐng)求上下文中間件
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using NetAsyncLocalExamples.Context;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NetAsyncLocalExamples.Middlewares
{
/// <summary>
/// 請(qǐng)求上下文
/// </summary>
public class RequestContextMiddleware : IMiddleware
{
protected readonly IServiceProvider ServiceProvider;
private readonly ILogger<RequestContextMiddleware> Logger;
public RequestContextMiddleware(IServiceProvider serviceProvider, ILogger<RequestContextMiddleware> logger)
{
ServiceProvider = serviceProvider;
Logger = logger;
}
public virtual async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var requestContext = new RequestContext();
using (RequestContext.SetContext(requestContext))
{
requestContext.TenantId = $"租戶(hù)ID:{DateTime.Now.ToString("yyyyMMddHHmmsss")}";
await next(context);
}
}
}
}
3.注冊(cè)中間件
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<RequestContextMiddleware>();
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
//增加上下文
app.UseMiddleware<RequestContextMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
一次賦值,到處使用
namespace NetAsyncLocalExamples.Pages
{
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
_logger.LogInformation($"測(cè)試獲取全局變量1:{RequestContext.Current.TenantId}");
}
public void OnGet()
{
_logger.LogInformation($"測(cè)試獲取全局變量2:{RequestContext.Current.TenantId}");
}
}
}
到此這篇關(guān)于.NET Core利用 AsyncLocal 實(shí)現(xiàn)共享變量的代碼詳解的文章就介紹到這了,更多相關(guān).NET Core共享變量?jī)?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在 .NET Core 中使用 Diagnostics (Diagnostic Source) 記錄跟蹤信息
今天給大家講一下在 .NET Core 2 中引入的全新 DiagnosticSource 事件機(jī)制,為什么說(shuō)是全新呢? 在以前的 .NET Framework 有心的同學(xué)應(yīng)該知道也有 Diagnostics,那么新的 .NET Core 中有什么變化呢?跟隨小編一起看看吧2021-06-06
asp.net中上傳圖片文件實(shí)現(xiàn)防偽圖片水印并寫(xiě)入數(shù)據(jù)庫(kù)
asp.net上傳圖片文件實(shí)現(xiàn)防偽圖片水印并寫(xiě)入數(shù)據(jù)庫(kù),需要的朋友可以參考下。2010-10-10
ASP.NET Core使用GraphQL第一章之Hello World
這篇文章主要給大家介紹了關(guān)于ASP.NET Core使用GraphQL第一章之Hello World的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11
Asp.NET MVC中使用SignalR實(shí)現(xiàn)推送功能
這篇文章主要為大家詳細(xì)介紹了Asp.NET MVC 中使用 SignalR 實(shí)現(xiàn)推送功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
URLRewriter最簡(jiǎn)單入門(mén)介紹 URLRewriter相關(guān)資源
配置好后,查看日志看到的狀態(tài)都是200,IIS直接認(rèn)為這個(gè)文件是存在的了, 而不是301,或302,這在某些情況下可能會(huì)不適用,比如:搜索引擎優(yōu)化時(shí)目錄或文件調(diào)整。2008-07-07
ASP.NET過(guò)濾HTML字符串方法總結(jié)
這篇文章主要介紹了ASP.NET過(guò)濾HTML字符串方法總結(jié),需要的朋友可以參考下2014-08-08
如何在ASP.Net Core使用分布式緩存的實(shí)現(xiàn)
這篇文章主要介紹了如何在ASP.Net Core使用分布式緩存的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
asp.net 水晶報(bào)表隔行換色實(shí)現(xiàn)方法
水晶報(bào)表隔行換色實(shí)現(xiàn)方法,需要的朋友可以參考下。2009-11-11
ASP.NET?MVC實(shí)現(xiàn)區(qū)域路由
這篇文章介紹了ASP.NET?MVC實(shí)現(xiàn)區(qū)域路由的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03

