免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

AsyncLocal 的避坑指南

AsyncLocal 的避坑指南 那么我们如何在 FooAsync 方法中修改 AsyncLocal 的值并且在 Main 方法中获取到修改后的值呢我们需要借助一个中介者让中介者来保存 AsyncLocal 的值然后在 FooAsync 方法中修改中介者的属性值这样就可以在 Main 方法中获取到修改后的值了。下面我们设计一个 ValueHolder 来保存 AsyncLocal 的值修改 Value 并不会修改 AsyncLocal 的值而是修改 ValueHolder 的属性值这样就不会触发 ExecutionContext 的 COW。我们还需要设计一个 ValueAccessor 来封装 ValueHolder 对值的访问和修改这样可以保证 ValueHolder 的值只能在 ValueAccessor 中被修改。class ValueAccessorT : IValueAccessorT { private static AsyncLocalValueHolderT _asyncLocal new AsyncLocalValueHolderT(); public T Value { get _asyncLocal.Value ! null ? _asyncLocal.Value.Value : default; set { _asyncLocal.Value ?? new ValueHolderT(); _asyncLocal.Value.Value value; } } } class ValueHolderT { public T Value { get; set; } } class Program { private static IValueAccessorstring _valueAccessor new ValueAccessorstring(); static async Task Main(string[] args) { _valueAccessor.Value A; Console.WriteLine($ValueAccessor before await FooAsync in Main: {_valueAccessor.Value}); await FooAsync(); Console.WriteLine($ValueAccessor after await FooAsync in Main: {_valueAccessor.Value}); } private static async Task FooAsync() { _valueAccessor.Value B; Console.WriteLine($ValueAccessor before await in FooAsync: {_valueAccessor.Value}); await Task.Delay(100); Console.WriteLine($ValueAccessor after await in FooAsync: {_valueAccessor.Value}); } }输出结果ValueAccessor before await FooAsync in Main: A ValueAccessor before await in FooAsync: B ValueAccessor after await in FooAsync: B ValueAccessor after await FooAsync in Main: BHttpContextAccessor 的实现原理我们常用的HttpContextAccessor通过HttpContextHolder来间接地在AsyncLocal中存储HttpContext。如果要更新 HttpContext只需要在 HttpContextHolder 中更新即可。因为 AsyncLocal 的值不会被修改更新 HttpContext 时 ExecutionContext 也不会出现 COW 的情况。不过 HttpContextAccessor 中的逻辑有点特殊它的 HttpContextHolder 是为保证清除 HttpContext 时这个 HttpContext 能在所有引用它的 ExecutionContext 中被清除可能因为修改 HttpContextHolder 之外的 AsyncLocal 数据导致 ExecutionContext 已经 COW 很多次了。下面是 HttpContextAccessor 的实现英文注释是原文中文注释是我自己的理解。/// /summary public class HttpContextAccessor : IHttpContextAccessor { private static readonly AsyncLocalHttpContextHolder _httpContextCurrent new AsyncLocalHttpContextHolder(); /// inheritdoc/ public HttpContext? HttpContext { get { return _httpContextCurrent.Value?.Context; } set { var holder _httpContextCurrent.Value; if (holder ! null) { // Clear current HttpContext trapped in the AsyncLocals, as its done. // 这边的逻辑是为了保证清除 HttpContext 时这个 HttpContext 能在所有引用它的 ExecutionContext 中被清除 holder.Context null; } if (value ! null) { // Use an object indirection to hold the HttpContext in the AsyncLocal, // so it can be cleared in all ExecutionContexts when its cleared. // 这边直接修改了 AsyncLocal 的值所以会导致 ExecutionContext 的 COW。新的 HttpContext 不会被传递到原先的 ExecutionContext 中。 _httpContextCurrent.Value new HttpContextHolder { Context value }; } } } private sealed class HttpContextHolder { public HttpContext? Context; } }但 HttpContextAccessor 的实现并不允许将新赋值的非 null 的 HttpContext 传递到外层的 ExecutionContext 中可以参考上面的源码及注释理解。class Program { private static IHttpContextAccessor _httpContextAccessor new HttpContextAccessor(); static async Task Main(string[] args) { var httpContext new DefaultHttpContext { Items new Dictionaryobject, object { { Name, A} } }; _httpContextAccessor.HttpContext httpContext; Console.WriteLine($HttpContext before await FooAsync in Main: {_httpContextAccessor.HttpContext.Items[Name]}); await FooAsync(); // HttpContext 被清空了下面这行输出 null Console.WriteLine($HttpContext after await FooAsync in Main: {_httpContextAccessor.HttpContext?.Items[Name]}); } private static async Task FooAsync() { _httpContextAccessor.HttpContext new DefaultHttpContext { Items new Dictionaryobject, object { { Name, B} } }; Console.WriteLine($HttpContext before await in FooAsync: {_httpContextAccessor.HttpContext.Items[Name]}); await Task.Delay(1000); Console.WriteLine($HttpContext after await in FooAsync: {_httpContextAccessor.HttpContext.Items[Name]}); } }输出结果HttpContext before await FooAsync in Main: A HttpContext before await in FooAsync: B HttpContext after await in FooAsync: B HttpContext after await FooAsync in Main:
返回列表