TL;DR ThreadLocal<T>.Value
指向相同的位置,如果 Thread.CurrentThread
保持不变。 AsyncLocal<T>.Value
是否有类似的东西(例如,SychronizationContext.Current
或 ExecutionContext.Capture()
是否适用于所有场景)?
想象一下,我们已经创建了一些数据结构的快照,该快照保存在线程本地存储(例如 ThreadLocal<T>
实例)中,并将其传递给辅助类以供以后使用。这个辅助类用于将这个数据结构恢复到快照状态。我们不想将此快照恢复到不同的线程上,因此我们可以检查创建了哪个线程辅助类。例如:
class Storage<T>
{
private ThreadLocal<ImmutableStack<T>> stackHolder;
public IDisposable Push(T item)
{
var bookmark = new StorageBookmark<T>(this);
stackHolder.Value = stackHolder.Value.Push(item);
return bookmark;
}
private class StorageBookmark<TInner> :IDisposable
{
private Storage<TInner> owner;
private ImmutableStack<TInner> snapshot;
private Thread boundThread;
public StorageBookmark(Storage<TInner> owner)
{
this.owner = owner;
this.snapshot = owner.stackHolder.Value;
this.boundThread = Thread.CurrentThread;
}
public void Dispose()
{
if(Thread.CurrentThread != boundThread)
throw new InvalidOperationException ("Bookmark crossed thread boundary");
owner.stackHolder.Value = snapshot;
}
}
}
有了这个,我们基本上将 StorageBookmark 绑定(bind)到特定线程,因此,绑定(bind)到 ThreadLocal 存储中特定版本的数据结构。我们通过在
Thread.CurrentThread
的帮助下确保我们不会跨越“线程上下文”来做到这一点现在,手头的问题。我们如何使用
AsyncLocal<T>
而不是 ThreadLocal<T>
实现相同的行为?准确地说,是否有类似于 Thread.CurrentThread
的东西可以在构建和使用时进行检查,以控制“异步上下文”没有被跨越(这意味着 AsyncLocal<T>.Value
将指向与构建书签时相同的对象)。似乎
SynchronizationContext.Current
或 ExecutionContext.Capture()
可能就足够了,但我不确定哪个更好,并且没有捕获(或者甚至可以在所有可能的情况下工作) 最佳答案
逻辑调用上下文与执行上下文具有相同的流语义,因此与 AsyncLocal
相同。知道了这一点,您可以在逻辑上下文中存储一个值以检测何时跨越“异步上下文”边界:
class Storage<T>
{
private AsyncLocal<ImmutableStack<T>> stackHolder = new AsyncLocal<ImmutableStack<T>>();
public IDisposable Push(T item)
{
var bookmark = new StorageBookmark<T>(this);
stackHolder.Value = (stackHolder.Value ?? ImmutableStack<T>.Empty).Push(item);
return bookmark;
}
private class StorageBookmark<TInner> : IDisposable
{
private Storage<TInner> owner;
private ImmutableStack<TInner> snapshot;
private Thread boundThread;
private readonly object id;
public StorageBookmark(Storage<TInner> owner)
{
id = new object();
this.owner = owner;
this.snapshot = owner.stackHolder.Value;
CallContext.LogicalSetData("AsyncStorage", id);
}
public void Dispose()
{
if (CallContext.LogicalGetData("AsyncStorage") != id)
throw new InvalidOperationException("Bookmark crossed async context boundary");
owner.stackHolder.Value = snapshot;
}
}
}
public class Program
{
static void Main()
{
DoesNotThrow().Wait();
Throws().Wait();
}
static async Task DoesNotThrow()
{
var storage = new Storage<string>();
using (storage.Push("hello"))
{
await Task.Yield();
}
}
static async Task Throws()
{
var storage = new Storage<string>();
var disposable = storage.Push("hello");
using (ExecutionContext.SuppressFlow())
{
Task.Run(() => { disposable.Dispose(); }).Wait();
}
}
}
关于c# - 如何检查 AsyncLocal<T> 是否在同一个 "async context"内被访问,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48224335/