我使用类似的模式HttpContext访问器
简化版本如下,Console.WriteLine(SimpleStringHolder.StringValue)
不应该为空。
public class SimpleStringHolder
{
private static readonly AsyncLocal<ValueHolder> CurrentHolder = new AsyncLocal<ValueHolder>();
public static string StringValue
{
get => CurrentHolder.Value?.StringValue;
set
{
var holder = CurrentHolder.Value;
if (holder != null)
{
holder.StringValue = null;
}
if (value != null)
{
CurrentHolder.Value = new ValueHolder() { StringValue = value };
}
}
}
private class ValueHolder
{
public string StringValue;
}
}
class Program
{
private static readonly AsyncLocal<string> currentValue = new AsyncLocal<string>();
public static void Main(string[] args)
{
var task = Task.Run(async () => await OutterAsync());
task.Wait();
}
public static async Task OutterAsync()
{
SimpleStringHolder.StringValue = "1";
await InnerAsync();
Console.WriteLine(SimpleStringHolder.StringValue); //##### the value is gone ######
}
public static async Task InnerAsync()
{
var lastValue = SimpleStringHolder.StringValue;
await Task.Delay(1).ConfigureAwait(false);
SimpleStringHolder.StringValue = lastValue; // comment this line will make it work
Console.WriteLine(SimpleStringHolder.StringValue); //the value is still here
}
}
在上面的代码中,OutterAsync
调用异步方法InnerAsync
, in InnerAsync
the StringValue
被设置,这使得 AsyncLocal 失去它的上下文OutterAsync
Console.WriteLine(SimpleStringHolder.StringValue);
一片空白。
我认为神奇之处在于 SimpleStringHolder 的属性集,删除以下代码将使事情变得正确。
if (holder != null)
{
holder.StringValue = null;
}
上面的代码按预期工作。
请帮我解释一下这是什么法术?
AsyncLocal<T>
存在是为了提供一种在异步执行上下文中保存值的机制。关键是您的示例涉及两个因素:
- An
await
允许方法返回调用者,这可能会更改上下文。与年长的ThreadLocal<T>
类型,当执行将控制权返回给方法时,它可能位于不同的线程中,即使来自async
上下文的观点是相同的。使用AsyncLocal<T>
确保上下文的状态在await
可等待对象完成后,将控制权返回给该方法。
- 直到发生需要更改上下文的事情之前,对象的当前状态
AsyncLocal<T>
对象是之前的任何对象。 IE。方法本质上继承了对象被调用时所处的状态。如果您正在处理简单的值,则不会出现任何意外,但是对于像您这样的引用类型ValueHolder
类型,唯一的东西AsyncLocal<T>
正在跟踪的是参考到那个物体。仍然只有该对象的一份副本,并且对任何给定此类对象的状态的更改都会像它们在有或没有异步上下文浮动的情况下总是所做的那样工作(即,通过对该对象的任何引用都可以看到它们)。
因此,在您提供的代码示例中:
-
OutterAsync()
设置StringValue
财产给"1"
,这会产生一个新的ValueHolder
正在创建的对象,以及StringValue
该对象的属性被设置为"1"
.
-
OutterAsync()
calls InnerAsync()
。然后该方法检索string
来自持有人的参考(间接......即通过SimpleStringHolder.StringValue
财产)。由于此时尚未对值或上下文进行任何更改,因此相同ValueHolder
在这种情况下使用对象,所以你得到"1"
back.
-
InnerAsync()
等待一个异步任务,这会导致创建一个新的执行上下文,以隔离对任务所做的更改AsyncValue<T>
反对该上下文。从此时起,对象的变化是not在不同的上下文中通过代码看到。例如,在OutterAsync()
method.
- 异步任务完成后
InnerAsync()
,然后该方法将一个新值设置为SimpleStringHolder.StringValue
财产。因为之前的上下文是继承的,当setter设置时holder.StringValue
to null
,它正在设置创建的对象的属性OutterAsync()
. But…因为代码处于新的上下文中,所以当 setter 为CurrentHolder.Value
属性,该更改与该上下文隔离。
- 当。。。的时候
InnerAsync()
方法最终完成,这完成了任务OutterAsync()
方法await
正在等待。这导致AsyncValue<T>
将其状态恢复到OutterAsync()
方法的上下文,与之前的上下文不同InnerAsync()
当它更新了SimpleStringHolder.StringValue
价值。具体来说,这个恢复的状态是对ValueHolder
最初设置的对象SimpleStringHolder
当。。。的时候holder.StringValue
属性被设置为 null。
- 所以,当
OutterAsync()
然后查看属性值,发现它设置为null。因为它被设置为空。
在您自己的实验中,您可以完全删除空分配,或者简单地省略分配SimpleStringHolder.StringValue
之后InnerAsync()
's await
语句(因为如果不进行赋值,则永远不会执行空赋值)。无论哪种方式,都不会发生空分配,因此先前分配的值仍然存在。
但是如果你确实进行了空赋值,调用者OutterAsync()
将恢复其上下文,然后恢复持有者对象引用,以及该持有者对象自己的引用string
参考已经设置为null
,所以这就是OutterAsync()
sees.
相关阅读:
AsyncLocal 在非 async/await 代码中有何作用?
为什么代码稍微重构一下,AsyncLocal 会返回不同的结果?
Does AsyncLocal也做那些事情ThreadLocal does?
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)