那么我们如何在 FooAsync 方法中修改 AsyncLocal 的值,并且在 Main 方法中获取到修改后的值呢?
我们需要借助一个中介者,让中介者来保存 AsyncLocal 的值,然后在 FooAsync 方法中修改中介者的属性值,这样就可以在 Main 方法中获取到修改后的值了。
下面我们设计一个 ValueHolder 来保存 AsyncLocal 的值,修改 Value 并不会修改 AsyncLocal 的值,而是修改 ValueHolder 的属性值,这样就不会触发 ExecutionContext 的 COW。
我们还需要设计一个 ValueAccessor 来封装 ValueHolder 对值的访问和修改,这样可以保证 ValueHolder 的值只能在 ValueAccessor 中被修改。
class ValueAccessor<T> : IValueAccessor<T>
{
private static AsyncLocal<ValueHolder<T>> _asyncLocal = new AsyncLocal<ValueHolder<T>>();
public T Value
{
get => _asyncLocal.Value != null ? _asyncLocal.Value.Value : default;
set
{
_asyncLocal.Value ??= new ValueHolder<T>();
_asyncLocal.Value.Value = value;
}
}
}
class ValueHolder<T>
{
public T Value { get; set; }
}
class Program
{
private static IValueAccessor<string> _valueAccessor = new ValueAccessor<string>();
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: B
我们常用的 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 AsyncLocal<HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextHolder>();
/// <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 Dictionary<object, 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 Dictionary<object, 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:
如果您发现该资源为电子书等存在侵权的资源或对该资源描述不正确等,可点击“私信”按钮向作者进行反馈;如作者无回复可进行平台仲裁,我们会在第一时间进行处理!
加入交流群
请使用微信扫一扫!