gpt4 book ai didi

c# - 我是否需要在逻辑请求结束时手动将 AsyncLocal 变量的值更改为 "Dispose"/"Release"

转载 作者:行者123 更新时间:2023-12-03 23:48:05 25 4
gpt4 key购买 nike

我读到了AsyncLocal<T>来自 the MSDN documentation ,但有一点我还不清楚。

我正在研究诸如上下文绑定(bind)缓存/内存之类的东西,其目的很简单,就是在逻辑请求中存储数据。这类似于旧的 HttpContext.Current ,其中数据存储在请求中,并将在请求结束时释放。然而,在我的情况下,我希望与环境无关,因此实现不限于例如ASP.NET MVC、ASP.NET Core、WCF 等,同时仍然能够存储和检索绑定(bind)到逻辑请求的数据,而无需在逻辑不同的请求之间共享它。

为了根据问题简化我的代码,它看起来有点像这样:

class ContextualStorageAccessor
{
// ConcurrentDictionary since it's okay if some parallel operations are used per logical request
private readonly AsyncLocal<ConcurrentDictionary<string, object>> _csm = new AsyncLocal<ConcurrentDictionary<string, object>>();

public ConcurrentDictionary<string, object> Storage
{
get
{
if (_csm.Value != null)
return _csm.Value;

_csm.Value = new ConcurrentDictionary<string, object>();

return _csm.Value;
}
}
}
ContextualStorageAccessor 的生命周期是单例。

现在的问题是:我会有一个 Value 的唯一实例吗?每个请求?换句话说,我是否需要继续为 _csm.Value 分配默认值?手动?或者我可以依赖应用程序本身的类型(例如,ASP.NET MVC、WCF 等)来处理它?

或者,换个说法:“异步流”的结束在哪里? ExecutionContext保证每个逻辑调用的唯一值将自动失效——在一个简单的场景中 null将分配给 AsyncLocal.Value — 在逻辑调用结束时(对于 ASP.NET MVC,一个 Web 请求;对于 WCF,一个操作)如果 AsyncLocal.Value用来?

最佳答案

如果您尝试此代码,您将看到每个新的异步流都会创建一个新值。所以答案应该是:是的,每个请求都应该有一个唯一的值。

private static readonly AsyncLocal<object> Item = new AsyncLocal<object>();

public static async Task Main()
{
async Task Request()
{
if (Item.Value is {})
{
Console.WriteLine("This should never happen.");
throw new InvalidOperationException("Value should be null here.");
}

Item.Value = new object();
}

await Task.Run(Request); // Just to be sure that Item.Value is initialized once.

await Task.WhenAll(
Task.Run(Request),
Task.Run(Request),
Task.Run(Request),
Task.Run(Request),
Task.Run(Request));

Console.WriteLine("finished");
}

DEMO

但我尝试了一个更复杂的示例来确定异步流程的结束位置。代码很简单但是大量使用 Console.WriteLine让它有点困惑。
public class Program
{
public static async Task Main()
{
await Task.Run(async () =>
{
Console.WriteLine("Async flow entered...");

// Init async value
if (Cache.Instance.Item.Value is {})
throw new InvalidOperationException("The async flow has just startet. A value should not be initialized.");

var newValue = new object();
Console.WriteLine($"Create: value = #{RuntimeHelpers.GetHashCode(newValue)}");

Cache.Instance.Item.Value = newValue;
await Foo();

Console.WriteLine("Async flow exitted.");
});

Console.WriteLine("Main finished.\n\n");
}

private static async Task Foo()
{
Console.WriteLine($"Foo: entered...");
await Bar();

Console.WriteLine($"Foo: getting value...");
var knownValue = Cache.Instance.Item.Value;
Console.WriteLine($"Foo: value = #{RuntimeHelpers.GetHashCode(knownValue)}");
Console.WriteLine($"Foo: exitted.");
}

private static async Task Bar()
{
Console.WriteLine($"Bar: entered...");
await Task.CompletedTask;
Console.WriteLine($"Bar: exitted.");
}
}

public sealed class Cache
{
public static Cache Instance = new Cache();

public AsyncLocal<object> Item { get; } = new AsyncLocal<object>(OnValueChanged);

private static void OnValueChanged(AsyncLocalValueChangedArgs<object> args)
{
Console.WriteLine($"OnValueChanged! Prev: #{RuntimeHelpers.GetHashCode(args.PreviousValue)} Current: #{RuntimeHelpers.GetHashCode(args.CurrentValue)}");
}
}

DEMO

该代码的输出是:
Async flow entered...
Create: value = #6044116
OnValueChanged! Prev: #0 Current: #6044116
Foo: entered...
Bar: entered...
Bar: exitted.
Foo: getting value...
Foo: value = #6044116
Foo: exitted.
Async flow exitted.
OnValueChanged! Prev: #6044116 Current: #0
Main finished.

值(value)流是预期的。这回答了是否将值分配给默认值的问题 - 是的。异步流程在 Task.Run 处结束完成,这就是将值分配给 default 的点.

但是,如果您更改 await Task.CompletedTask;,事情就会变得有趣。在 Barawait Task.Delay(1); .输出看起来有很大不同:
Async flow entered...
Create: value = #6044116
OnValueChanged! Prev: #0 Current: #6044116
Foo: entered...
Bar: entered...
OnValueChanged! Prev: #6044116 Current: #0
OnValueChanged! Prev: #0 Current: #6044116
Bar: exitted.
Foo: getting value...
Foo: value = #6044116
Foo: exitted.
Async flow exitted.
OnValueChanged! Prev: #6044116 Current: #0
Main finished.


OnValueChanged! Prev: #0 Current: #6044116
OnValueChanged! Prev: #6044116 Current: #0

奇怪的部分在 Bar 之后开始已输入。看起来 await Task.Delay(1)打破异步流程。但是值(value)被正确地恢复了。在这里,我至少可以通过猜测来做出解释。

真正有趣的事情发生在 main 完成之后。值又恢复清零了……我这里缺乏想象力。我完全不知道为什么以及如何在 Task.Run 之后恢复该值已经完成,异步流程也应该完成。这让我感觉在程序结束之前 GC 无法清除对象。

关于c# - 我是否需要在逻辑请求结束时手动将 AsyncLocal 变量的值更改为 "Dispose"/"Release",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61341540/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com