我正在研究桌面winform应用程序的一个非常大的旧代码库.在这个代码库中,有很多操作在后台线程中执行,主要是使用BackgroundWorker.
此代码库中的一个常见模式是通过将工件绑定到正在执行的线程来隐藏复杂性.例如,数据库连接和事务存储在[ThreadStatic]字段中.
我正在尝试更改此设置,并开始使用async/await代码,并从在池的任何线程中运行任务中受益,并允许任务通过使用继续在任何其他线程中执行ConfigureAwait(false).我知道这[ThreadStatic]不好玩async/await,我已经阅读了几个答案,建议使用AsyncLocal<T>.
鉴于我正在开发大型代码库,如前所述,我无法async/await一次性切换到任何地方,我必须逐步进行此更改.所以之前的代码[ThreadStatic]将改为AsyncLocal<T>,但代码的大部分将继续使用,BackgroundWorker并且不会访问单行async/await代码.
问题
这会有用吗?我需要能够定义某种可以使用我的新async/await代码的上下文流程,并且还继续使用我的旧非异步代码,这些代码依赖于[ThreadStatic]保持每个线程内容彼此独立.
如果我完全错了,走错了路,建议非常受欢迎.
我正在努力寻找简单的文档来说明其AsyncLocal<T>作用。
我写了一些测试,我认为这些测试告诉我答案是“是”,但如果有人能证实这一点那就太好了!(特别是因为我不知道如何编写对线程和延续上下文有明确控制的测试......所以它们可能只是巧合地工作!)
据我了解,ThreadLocal将保证如果您在不同的线程上,那么您将获得对象的不同实例。
ThreadLocal对象已经被使用过的线程)。await却不太愉快。您继续的线程(即使)不能保证与您开始的线程相同,因此您可能无法从另一端.ConfigureAwait(true)返回相同的对象。ThreadLocal相反,AsyncLocal 确实保证您将在调用的两侧获得相同的对象await。
但我找不到任何地方实际上说AsyncLocal将获得特定于初始线程的值,首先!
IE:
MyAsyncMethod),它在调用的任一侧引用其类中的“共享”AsyncLocal字段 ( ) 。myAsyncLocalawait我知道,对于 ,的每次单独调用MyAsyncMethod,myAsyncLocal.Value将在等待之前和之后返回相同的对象(假设没有任何内容重新分配它)
但是否能保证每次调用首先都会查看不同的对象?
正如一开始提到的,我创建了一个测试来尝试自己确定这一点。以下测试一致通过
public class AssessBehaviourOfAsyncLocal
{
private class StringHolder
{
public string HeldString { get; set; }
}
[Test, Repeat(10)]
public void RunInParallel()
{
var reps = Enumerable.Range(1, 100).ToArray();
Parallel.ForEach(reps, index => …Run Code Online (Sandbox Code Playgroud) 当我打电话时WrapperAsync AsyncLocalContext.Value返回空值。当我在方法外运行相同的代码块时,在Main方法中,AsyncLocalContext.Value不为空(这是我所期望的)。
功能完全相同,但结果不同。这是Asynclocal班级的错误还是有其他解释?
internal class Program
{
private static readonly AsyncLocal<string> AsyncLocalContext = new AsyncLocal<string>();
private static void Main()
{
const string text = "surprise!";
WrapperAsync(text).Wait();
Console.WriteLine("Get is null: " + (AsyncLocalContext.Value == null));
// AsyncLocalContext.Value is null
var value = GetValueAsync(text).Result;
AsyncLocalContext.Value = value;
Console.WriteLine("Get is null: " + (AsyncLocalContext.Value == null));
// AsyncLocalContext.Value is not null
Console.Read();
}
private static async Task WrapperAsync(string text)
{
var value = await GetValueAsync(text); …Run Code Online (Sandbox Code Playgroud) 我不太完全理解这种情况,其中 AsyncLocal实例是在AuthenticationHandler中的某个点设置的,但当它被注入到constructor中时却没有到达控制器。
我已经使它类似于IHttpContextAccessor 的工作方式,但仍然相距甚远。但是,如果我从Middleware设置 AsyncLocal ,它就会到达控制器。另外,从AuthenticationHandler设置 HttpContext.Items 属性也可以正常工作。
问题: HttpContext 如何能够一直保留 Items 属性内容,并且 ASP.NET 运行时是否出于某种安全原因处理我的 DomainContextAccessor 捕获的 ExecutionContext(因为它的设置位置)?
我制作了一个示例应用程序来演示此用例。我真的很感激有人能阐明这个问题。